如何正确调用 Grand Central Dispatch

How to call GrandCentralDispatch properly

我正在尝试从我的 API 加载自定义数据类型并将其显示在我的 iOS 应用程序的登录页面上。

据我所知,我应该这样调用:

override func viewDidLoad() {
  performSelector(inBackground: #selector(fetchJSON), with: nil)
  let myCustomDataType = //how so I get this back
  tableView.reloadData()
 //...
}

显然 fetchJSON 的声明应该在另一个文件中,这样我就不会用它不需要做的事情来填满我的控制器。但是我需要这个函数来 return [MyCustomDataType] 的列表并将它们显示在我的登陆页面上。

func fetchJSON() -> [MyCustomDataType] {
  //get api, fetch data, put it in my custom data type list
  return myCustomDataType
}

我使用闭包吗?创建一个全局 [MyCystomDataType]。或者我如何实现这一目标? 请注意任务是异步的,因为我显示 table 个单元格有点像 facebook 或 instagram 新闻提要页面。

performSelector(inBackground:) 不是 GCD。这是来自 OS X 10.5 的 GCD 之前的方法,您几乎不应该使用它。 (自从 10.6 引入 GCD 以来,我认为我没有理由使用它。)

一般来说,对于网络请求,您根本不需要直接使用 GCD。 URLSessionTask 已经是异步的。有关更多信息,请参阅 Fetching Website data into memory。如那里所示,您经常需要使用 GCD (DispatchQueue.main.async) 将 return 数据传送到 UI,但您并不需要它启动请求。

但对于您的基本问题,答案是到 viewDidLoad 完成时,您还没有数据。您需要能够处理没有数据的情况,并适当地绘制您的 UI。当数据出现时,您可以更新 UI.

如果 fetchJSON 异步加载数据,您必须添加完成处理程序

func fetchJSON(completion: @escaping ([MyCustomDataType]) -> Void){
    API.fetch { fetchedData in
        completion(fetchedData)
    }
}

然后在后台线程上调用 fetchJSON 并在主线程上重新加载 table 视图

override func viewDidLoad() {
   DispatchQueue.global().async {
      self.fetchJSON { data in 
          DispatchQueue.main.async {
              self.myCustomDataType = data
              self.tableView.reloadData()
          }
      }
   }
}

流行的异步 API 像 URLSessionAlamofire 隐式地在后台线程上执行它们的网络请求。如果你打算使用其中一个,你可以省略第一个 async 块。

performSelector 是一个过时的 objective-c-ish API。不要在 Swift

中使用它

正如 Vadian 所说 (+1),您想使用异步模式,而使用闭包是一个很好的模式。但问题不仅仅是如何交回数据,而是在出错的情况下,如何报告Error。我们经常使用 Result<Success, Failure> 模式来实现这一点。例如。而不是 [MyCustomDataType] 参数,它可能具有 Result<[MyCustomDataType], Error> 类型。

例如,假设您正在通过 URLSession 执行您的请求。那么你可能会有这样的例程:

enum ApiError: Error {
    case networkError(Data?, URLResponse?)
}

func fetchJSON<T: Decodable>(_ request: URLRequest, queue: DispatchQueue = .main, completion: @escaping (Result<T, Error>) -> Void) {
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        // detect and report error
        guard
            error == nil,
            let responseData = data,
            let httpResponse = response as? HTTPURLResponse,
            200 ..< 300 ~= httpResponse.statusCode
        else {
            queue.async {
                completion(.failure(error ?? ApiError.networkError(data, response)))
            }
            return
        }

        // no network error, so let's parse the response
        do {
            let responseObject = try JSONDecoder().decode(T.self, from: responseData)
            queue.async {
                completion(.success(responseObject))
            }
        } catch let parseError {
            queue.async {
                completion(.failure(parseError))
            }
        }
    }
    task.resume()
}

并执行特定请求:

func fetchMyCustomDataTypes(completion: @escaping (Result<[MyCustomDataType], Error>) -> Void) {
    let request: URLRequest = ... // build your request here
    fetchJSON(request) { result in
        completion(result)
    }
}

现在,不要太迷失在上面的细节中,因为您的实现可能会有所不同(例如,您可以轻松地使用 Alamofire 或其他)。关键是我们有一个包含 Swift.Result 参数的完成处理程序闭包,通过该参数可以将数据或错误信息提供给调用者。

现在,调用者可以根据结果是 success 还是 failure 采取相应行动:

override func viewDidLoad() {
    super.viewDidLoad()       // make sure to call `super`

    fetchMyCustomDataTypes { result in
        switch result {
        case .failure(let error):
            // handle error here, e.g., report the error in the UI; or at the minimum, during development, just print the error
            print(error)
    
        case .success(let objects):
            // use the `[MyCustomDataType]` array, `objects` here, e.g.
            self.objects = objects
            self.tableView.reloadData()
        }
    }
}

但是来电者没有使用performSelector。它也不需要将此网络请求分派到后台队列,因为网络请求本质上已经是异步的。只需调用 fetchJSON 并指定完成处理程序中必须发生的 UI 更新。