在iOS中管理对Web API的异步调用

时间:2022-12-28 10:59:32

I am fetching data (news articles) in JSON format from a web service. The fetched data needs to be converted to an Article object and that object should be stored or updated in the database. I am using Alamofire for sending requests to the server and Core Data for database management.
My approach to this was to create a DataFetcher class for fetching JSON data and converting it to Article object:

我从Web服务获取JSON格式的数据(新闻文章)。需要将获取的数据转换为Article对象,并且应该在数据库中存储或更新该对象。我正在使用Alamofire向服务器发送请求,并使用Core Data进行数据库管理。我的方法是创建一个DataFetcher类来获取JSON数据并将其转换为Article对象:

class DataFetcher {

    var delegate:DataFetcherDelegate?

    func fetchArticlesFromUrl(url:String, andCategory category:ArticleCategory) {
        //convert json to article
        //send articles to delegate

        getJsonFromUrl(url) { (json:JSON?,error:NSError?) in
            if error != nil {
                print("An error occured while fetching json : \(error)")
            }
            if json != nil {
                let articles = self.getArticleFromJson(json!,andCategory: category)
                self.delegate?.receivedNewArticles(articles, fromCategory: category)
            }
        }
    }

After I fetch the data I send it to DataImporter class to store it in database:

获取数据后,我将其发送到DataImporter类以将其存储在数据库中:

func receivedNewArticles(articles: [Article], fromCategory category:ArticleCategory) {

        //update the database with new articles
        //send articles to delegate
        delegate?.receivedUpdatedArticles(articles, fromCategory:category)
    }

The DataImporter class sends the articles to its delegate that is in my case the ViewController. This pattern was good when I had only one API call to make (that is fetchArticles), but now I need to make another call to the API for fetching categories. This call needs to be executed before the fetchArticles call in the ViewController.
This is the viewDidLoad method of my viewController:

DataImporter类将文章发送到它的委托,在我的情况下是ViewController。当我只进行一次API调用(即fetchArticles)时,这种模式很好,但现在我需要再次调用API来获取类别。此调用需要在ViewController中调用fetchArticles之前执行。这是我的viewController的viewDidLoad方法:

override func viewDidLoad() {
        super.viewDidLoad()

        self.dataFetcher = DataFetcher()
        let dataImporter = DataImporter()
        dataImporter.delegate = self
        self.dataFetcher?.delegate = dataImporter

        self.loadCategories()
        self.loadArticles()
    }

My questions are:

我的问题是:

  1. What is the best way to ensure that one the call to the API gets executed before the other one?
  2. 确保在另一个API之前执行API调用的最佳方法是什么?
  3. Is the pattern that I implemented good since I need to make different method for different API calls?
  4. 我实现的模式是否很好,因为我需要为不同的API调用创建不同的方法?

1 个解决方案

#1


2  

  1. What is the best way to ensure that one the call to the API gets executed before the other one?
  2. 确保在另一个API之前执行API调用的最佳方法是什么?

If you want to ensure that two or more asynchronous functions execute sequentially, you should first remember this:

如果要确保两个或多个异步函数按顺序执行,则应首先记住:

  • If you implement a function which calls an asynchronous function, the calling function becomes asynchronous as well.

    如果实现调用异步函数的函数,则调用函数也会异步。

  • An asynchronous function should have a means to signal the caller that it has finished.

    异步函数应该有一种方法来通知调用者它已经完成。

If you look at the network function getJsonFromUrl - which is an asynchronous function - it has a completion handler parameter which is one approach to signal the caller that the underlying task (a network request) has finished.

如果你看一下网络函数getJsonFromUrl - 它是一个异步函数 - 它有一个完成处理程序参数,这是一种向调用者发出信号通知基础任务(网络请求)已经完成的方法。

Now, fetchArticlesFromUrl calls the asynchronous function getJsonFromUrl and thus becomes asynchronous as well. However, in your current implementation it has no means to signal the caller that its underlying task (getJsonFromUrl) has finished. So, you first need to fix this, for example, through adding an appropriate completion handler and ensuring that the completion handler will eventually be called from within the body.

现在,fetchArticlesFromUrl调用异步函数getJsonFromUrl,因此也变为异步。但是,在当前的实现中,它无法向调用者发出其基础任务(getJsonFromUrl)已完成的信号。因此,您首先需要解决此问题,例如,通过添加适当的完成处理程序并确保最终将从正文中调用完成处理程序。

The same is true for your function loadArticles and loadCategories. I assume, these are asynchronous and require a means to signal the caller that the underlying task has finished - for example, by adding a completion handler parameter.

您的函数loadArticles和loadCategories也是如此。我假设,这些是异步的,需要一种方法来通知调用者底层任务已经完成 - 例如,通过添加一个完成处理程序参数。

Once you have a number of asynchronous functions, you can chain them - that is, they will be called sequentially:

一旦你有了许多异步函数,就可以将它们链接起来 - 也就是说,它们会被顺序调用:

Given, two asynchronous functions:

给定,两个异步函数:

func loadCategories(completion: (AnyObject?, ErrorType?) -> ())
func loadArticles(completion: (AnyObject?, ErrorType?) -> ())

Call them as shown below:

如下所示调用它们:

loadCategories { (categories, error) in
    if let categories = categories {
        // do something with categories:
        ...
        // Now, call loadArticles:
        loadArticles { (articles, error) in
            if let articles = articles {
                // do something with the articles
                ...
            } else {
                // handle error:
                ...
            }
        }
    } else {
        // handler error
        ...
    }
}
  1. Is the pattern that I implemented good since I need to make different method for different API calls?
  2. 我实现的模式是否很好,因为我需要为不同的API调用创建不同的方法?

IMHO, you should not merge two functions into one where one performs the network request and the other processes the returned data. Just let them separated. The reason is, you might want to explicitly specify the "execution context" - that is, the dispatch queue, where you want the code to be executed. Usually, Core Data, CPU bound functions and network functions should not or cannot share the same dispatch queue - possibly also due to concurrency constraints. Due to this, you may want to have control over where your code executes through a parameter which specifies a dispatch queue.

恕我直言,你不应该将两个函数合并为一个,其中一个执行网络请求,另一个处理返回的数据。让它们分开。原因是,您可能希望显式指定“执行上下文” - 即调度队列,您希望在其中执行代码。通常,核心数据,CPU绑定功能和网络功能不应该或不能共享相同的调度队列 - 可能也是由于并发约束。因此,您可能希望通过指定调度队列的参数来控制代码的执行位置。

If processing data may take perceivable time (e.g. > 100ms) don't hesitate and execute it asynchronously on a dedicated queue (not the main queue). Chain several asynchronous functions as shown above.

如果处理数据可能需要可感知的时间(例如> 100ms),请不要犹豫,并在专用队列(而不是主队列)上异步执行它。链接几个异步函数,如上所示。

So, your code may consist of four asynchronous functions, network request 1, process data 1, network request 2, process data 2. Possibly, you need another function specifically for storing the data into Core Data.

因此,您的代码可能包含四个异步函数,网络请求1,过程数据1,网络请求2,过程数据2.可能,您需要另一个专门用于将数据存储到Core Data中的函数。

Other hints:

其他提示:

Unless there's a parameter which can be set by the caller and which explicitly specifies the "execution context" (e.g. a dispatch queue) where the completion handler should be called on, it is preferred to submit the call of the completion handler on a concurrent global dispatch queue. This performs faster and avoids dead locks. This is in contrast to Alamofire that usually calls the completion handlers on the main thread per default and is prone to dead locks and also performs suboptimal. If you can configure the queue where the completion handler will be executed, please do this.

除非有一个参数可以由调用者设置并且明确指定应该调用完成处理程序的“执行上下文”(例如调度队列),否则最好在并发全局上提交完成处理程序的调用。调度队列。这样可以更快地执行并避免死锁。这与Alamofire相反,Alamofire通常在默认情况下调用主线程上的完成处理程序,并且容易出现死锁并且执行次优。如果您可以配置将执行完成处理程序的队列,请执行此操作。

Prefere to execute functions and code on a dispatch queue which is not associated to the main thread - e.g. not the main queue. In your code, it seems, the bulk of processing the data will be executed on the main thread. Just ensure that UIKit methods will execute on the main thread.

优先执行与主线程无关的调度队列上的函数和代码 - 例如不是主队列。在您的代码中,似乎处理数据的大部分将在主线程上执行。只需确保UIKit方法将在主线程上执行。

#1


2  

  1. What is the best way to ensure that one the call to the API gets executed before the other one?
  2. 确保在另一个API之前执行API调用的最佳方法是什么?

If you want to ensure that two or more asynchronous functions execute sequentially, you should first remember this:

如果要确保两个或多个异步函数按顺序执行,则应首先记住:

  • If you implement a function which calls an asynchronous function, the calling function becomes asynchronous as well.

    如果实现调用异步函数的函数,则调用函数也会异步。

  • An asynchronous function should have a means to signal the caller that it has finished.

    异步函数应该有一种方法来通知调用者它已经完成。

If you look at the network function getJsonFromUrl - which is an asynchronous function - it has a completion handler parameter which is one approach to signal the caller that the underlying task (a network request) has finished.

如果你看一下网络函数getJsonFromUrl - 它是一个异步函数 - 它有一个完成处理程序参数,这是一种向调用者发出信号通知基础任务(网络请求)已经完成的方法。

Now, fetchArticlesFromUrl calls the asynchronous function getJsonFromUrl and thus becomes asynchronous as well. However, in your current implementation it has no means to signal the caller that its underlying task (getJsonFromUrl) has finished. So, you first need to fix this, for example, through adding an appropriate completion handler and ensuring that the completion handler will eventually be called from within the body.

现在,fetchArticlesFromUrl调用异步函数getJsonFromUrl,因此也变为异步。但是,在当前的实现中,它无法向调用者发出其基础任务(getJsonFromUrl)已完成的信号。因此,您首先需要解决此问题,例如,通过添加适当的完成处理程序并确保最终将从正文中调用完成处理程序。

The same is true for your function loadArticles and loadCategories. I assume, these are asynchronous and require a means to signal the caller that the underlying task has finished - for example, by adding a completion handler parameter.

您的函数loadArticles和loadCategories也是如此。我假设,这些是异步的,需要一种方法来通知调用者底层任务已经完成 - 例如,通过添加一个完成处理程序参数。

Once you have a number of asynchronous functions, you can chain them - that is, they will be called sequentially:

一旦你有了许多异步函数,就可以将它们链接起来 - 也就是说,它们会被顺序调用:

Given, two asynchronous functions:

给定,两个异步函数:

func loadCategories(completion: (AnyObject?, ErrorType?) -> ())
func loadArticles(completion: (AnyObject?, ErrorType?) -> ())

Call them as shown below:

如下所示调用它们:

loadCategories { (categories, error) in
    if let categories = categories {
        // do something with categories:
        ...
        // Now, call loadArticles:
        loadArticles { (articles, error) in
            if let articles = articles {
                // do something with the articles
                ...
            } else {
                // handle error:
                ...
            }
        }
    } else {
        // handler error
        ...
    }
}
  1. Is the pattern that I implemented good since I need to make different method for different API calls?
  2. 我实现的模式是否很好,因为我需要为不同的API调用创建不同的方法?

IMHO, you should not merge two functions into one where one performs the network request and the other processes the returned data. Just let them separated. The reason is, you might want to explicitly specify the "execution context" - that is, the dispatch queue, where you want the code to be executed. Usually, Core Data, CPU bound functions and network functions should not or cannot share the same dispatch queue - possibly also due to concurrency constraints. Due to this, you may want to have control over where your code executes through a parameter which specifies a dispatch queue.

恕我直言,你不应该将两个函数合并为一个,其中一个执行网络请求,另一个处理返回的数据。让它们分开。原因是,您可能希望显式指定“执行上下文” - 即调度队列,您希望在其中执行代码。通常,核心数据,CPU绑定功能和网络功能不应该或不能共享相同的调度队列 - 可能也是由于并发约束。因此,您可能希望通过指定调度队列的参数来控制代码的执行位置。

If processing data may take perceivable time (e.g. > 100ms) don't hesitate and execute it asynchronously on a dedicated queue (not the main queue). Chain several asynchronous functions as shown above.

如果处理数据可能需要可感知的时间(例如> 100ms),请不要犹豫,并在专用队列(而不是主队列)上异步执行它。链接几个异步函数,如上所示。

So, your code may consist of four asynchronous functions, network request 1, process data 1, network request 2, process data 2. Possibly, you need another function specifically for storing the data into Core Data.

因此,您的代码可能包含四个异步函数,网络请求1,过程数据1,网络请求2,过程数据2.可能,您需要另一个专门用于将数据存储到Core Data中的函数。

Other hints:

其他提示:

Unless there's a parameter which can be set by the caller and which explicitly specifies the "execution context" (e.g. a dispatch queue) where the completion handler should be called on, it is preferred to submit the call of the completion handler on a concurrent global dispatch queue. This performs faster and avoids dead locks. This is in contrast to Alamofire that usually calls the completion handlers on the main thread per default and is prone to dead locks and also performs suboptimal. If you can configure the queue where the completion handler will be executed, please do this.

除非有一个参数可以由调用者设置并且明确指定应该调用完成处理程序的“执行上下文”(例如调度队列),否则最好在并发全局上提交完成处理程序的调用。调度队列。这样可以更快地执行并避免死锁。这与Alamofire相反,Alamofire通常在默认情况下调用主线程上的完成处理程序,并且容易出现死锁并且执行次优。如果您可以配置将执行完成处理程序的队列,请执行此操作。

Prefere to execute functions and code on a dispatch queue which is not associated to the main thread - e.g. not the main queue. In your code, it seems, the bulk of processing the data will be executed on the main thread. Just ensure that UIKit methods will execute on the main thread.

优先执行与主线程无关的调度队列上的函数和代码 - 例如不是主队列。在您的代码中,似乎处理数据的大部分将在主线程上执行。只需确保UIKit方法将在主线程上执行。