首页 > 解决方案 > 在后台线程而不是 iOS 中的主 UI 线程上调用完成处理程序

问题描述

我有一个网络类,可以从服务器获取数据。在该类的完成处理程序中,它看起来像这样:

func fetchData(url: URL, completion: @escaping (Result<Data, MyError>) -> Void) {
        let request = URLRequest(url: url)
        fetch(request: request) { (result: Result<Data, MyError>) in
            switch result {
            case .success(let response):
                DispatchQueue.main.async {
                    completion(.success(response))
                }
            case .failure(let error):
                DispatchQueue.main.async {
                    completion(.failure(error))
                }
            }
        }
    }

如果我fetchData从 ViewController 调用此方法,我会在主线程上获得回调,并且我不必在主线程上重新加载我的集合视图。然后我尝试为我的视图控制器添加一个视图模型。所以流程看起来更像:

ViewController -> ViewModel (fetchData) -> Networking (fetchData)

基本上每个类只调用一个看起来与上面的 fetchData 方法完全相同的方法,向上传递完成。在 ViewController 中,我是否需要再次检查我是否在主线程上。iOS 可以在这些调用期间切换线程吗?我问是因为我确实收到了关于更新 UI 的警告,但没有在主线程上调用一次。但我不确定这是否是这个电话的误报,因为我还有其他网络电话来获取图像,也许我在其他地方搞砸了其他东西。但基本上,我只是问我是否不执行任何其他 GCD 类型的任务,而只使用完成处理程序并从调用主线程的单个网络调用中冒泡完成,我是否需要在某个地方再次检查向上链(如在 ViewController 中)。

标签: iosswiftmultithreading

解决方案


您还没有提供“这些调用”的代码,因此无法确定代码是否会被分派到另一个队列中,但是,系统不会在执行代码时随意切换到另一个队列。您需要显式或隐式调度到另一个队列。上面的代码在您调用时包含对主队列的显式分派和对另一个队列的隐式分派fetch(该代码中的某处将隐式分派到另一个队列,可能在您看不到源代码的代码中)。

作为对您问题的简单回答,如果您在所示的完成处理程序中分派到主队列,并且没有其他称为“进一步向上”的代码执行异步工作或显式分派到主队列以外的队列,您可以确定将在主队列上继续执行。

此外,您可以通过直接调用上游完成处理程序来简化代码:

func fetchData(url: URL, completion: @escaping (Result<Data, MyError>) -> Void) {
    let request = URLRequest(url: url)
    fetch(request: request) { (result: Result<Data, MyError>) in
        DispatchQueue.main.async {
           completion(response))
        }
    }
}

在设计代码时,您应该采用以下两种方法之一并坚持下去:

  1. 尽早调度到主队列。这种方法通常被其他人使用的框架所采用;例如,AFNetworking 明确记录了完成处理程序被分派到主队列中,因此您无需担心。这种方法的缺点是程序员可能不会阅读文档并且可能会防御性地分派到主队列,导致双重异步分派,或者他们可能不会更新 UI 并且不需要主线程执行。这是一个开销,但不太可能是一个主要问题。

  2. 永远不要分派到主队列上,如果需要,请依赖调用代码来分派。当所有代码都是一个解决方案的一部分并且程序员“知道”他们最终需要分派到主队列上时,这种方法可能更常见。这种方法的优点是您可以推迟(如果不需要,可能完全避免)将工作分派到主队列。缺点是如果你忘记这样做你会收到警告和主线程违规


推荐阅读