在iOS中管理对Web API的异步调用 [英] Managing asynchronous calls to web API in iOS

查看:134
本文介绍了在iOS中管理对Web API的异步调用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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:

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)
            }
        }
    }

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

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

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

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

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

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:

override func viewDidLoad() {
        super.viewDidLoad()

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

        self.loadCategories()
        self.loadArticles()
    }

我的问题是:

  1. 确保一个API调用在另一个调用之前执行的最佳方法是什么?
  2. 由于需要为不同的API调用使用不同的方法,因此实现的模式是否良好?

推荐答案

  1. 确保一种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.

如果您查看网络函数getJsonFromUrl-这是一个异步函数-它具有一个 completion handler 参数,该参数是 one 的一种方法,用于向调用者发出信号基础任务(网络请求)已完成.

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.

现在,fetchArticlesFromUrl调用异步函数getJsonFromUrl,因此也变为异步.但是,在您当前的实现中,它没有任何信号通知调用方其基础任务(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.

函数loadArticlesloadCategories也是一样.我假设它们是异步的,并且需要一种方法来向调用方发出基本任务已完成的信号,例如,通过添加完成处理程序参数.

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.

一旦您具有许多异步函数,就可以链接它们-即,它们将被依次称为 :

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

给出两个异步函数:

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

按如下所示调用它们:

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. 由于需要为不同的API调用使用不同的方法,因此实现的模式是否良好?

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

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.

如果处理数据可能会花费可察觉的时间(例如> 100ms),请毫不犹豫地在专用队列(而不是主队列)上异步执行它.链接多个异步函数,如上所示.

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.

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

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.

其他提示:

除非调用者可以设置一个参数,并且该参数明确指定应在其中调用完成处理程序的执行上下文"(例如,调度队列),否则最好在以下位置提交完成处理程序的调用: 并发全局调度队列.这样可以更快地执行并避免死锁.这与Alamofire不同,后者通常默认情况下在默认情况下在主线程上调用完成处理程序,并且容易出现死锁,并且执行效果欠佳.如果您可以配置将在其中执行完成处理程序的队列,请执行此操作.

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.

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

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.

这篇关于在iOS中管理对Web API的异步调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆