您不想“停止执行”。您想采用异步模式。
传统模式是“完成处理程序”闭包参数。例如,与其让callAPI 直接更新portfolio,不如直接在完成处理程序闭包中传回结果,例如:
let decoder = JSONDecoder() // no need to repeatedly instantiate a decoder used by all requests; move out of the method
@discardableResult
func callAPI(
endpoint: String,
queue: DispatchQueue = .main,
completion: @escaping (Result<PriceResponse, Error>) -> Void
) -> URLSessionTask? {
guard let url = URL(string: endpoint) else {
queue.async { completion(.failure(URLError(.badURL))) }
return nil
}
let task = URLSession.shared.dataTask(with: url) { [self] data, response, error in
guard let data = data, error == nil else {
queue.async { completion(.failure(error ?? URLError(.badServerResponse))) }
return
}
do {
let priceResponse = try decoder.decode(PriceResponse.self, from: data)
queue.async { completion(.success(priceResponse)) }
} catch let parseError {
queue.async { completion(.failure(parseError)) }
}
}
task.resume()
return task
}
注意带有Result<PriceResponse,Error> 参数的完成处理程序闭包。这就是我们将数据传回的方式。我提供了一个可选参数queue,因此您可以指定要调用完成处理程序的队列。
(我也传回URLSessionTask,以防您将来想添加取消逻辑,但这与此处无关。)
然后setPortfolioTableList 将使用该Result 来更新callAPI 的完成处理程序中的模型:
func setPortfolioTableList(portfolio: Portfolio, completion: (() -> Void) = nil) {
let group = DispatchGroup()
for (symbol, endpoint) in portfolio.endpointList {
group.enter()
callAPI(endpoint: endpoint) { result in
defer { group.leave() }
switch result {
case .failure(let error): print(error)
case .success(let priceResponse): portfolio.portfolioList[symbol] = priceResponse
}
}
}
group.notify(queue: .main) {
// do something at the end
...
// when done, call this methods completion handler
completion?()
}
}
注意,我也为这个方法提供了一个可选的完成处理程序,因此如果您需要,它的调用者可以知道所有网络请求何时完成。我使用DispatchGroup 来跟踪是否所有这些异步网络请求都已完成。
现在,显然,您的 Portfolio 是 class(引用类型),并且随着这些单独的网络请求完成,您将逐步更新它。你可能不想要那个。例如,如果 UI 显示“上次更新时间是 10 分钟前”,但一半的价格刚刚更新,一半没有更新,这可能是不可取的。您可能会考虑推迟模型对象的更新,直到一切都完成,例如:
func setPortfolioTableList(portfolio: Portfolio, completion: @escaping () -> Void) {
let group = DispatchGroup()
var prices: [String: PriceResponse] = [:]
for (symbol, endpoint) in portfolio.endpointList {
group.enter()
callAPI(endpoint: endpoint) { result in
defer { group.leave() }
switch result {
case .failure(let error): print(error)
case .success(let priceResponse): prices[symbol] = priceResponse
}
}
}
group.notify(queue: .main) {
// do something at the end
portfolio.portfolioList = prices
// when done, call this methods completion handler
completion()
}
}
我从您对dataTask(with:completion:) 的使用推断出您还没有使用 Swift 并发系统(又名async-await)。如果是的话,实现会大大简化:
func callAPI(endpoint: String) async throws -> PriceResponse {
guard let url = URL(string: endpoint) else {
throw URLError(.badURL)
}
let (data, _) = try await URLSession.shared.data(from: url)
return try decoder.decode(PriceResponse.self, from: data)
}
func setPortfolioTableList(portfolio: Portfolio) async throws -> [String: PriceResponse] {
try await withThrowingTaskGroup(of: (String, PriceResponse).self) { [self] group in
for (symbol, endpoint) in portfolio.endpointList {
group.addTask { try await (symbol, callAPI(endpoint: endpoint)) }
}
return try await group.reduce(into: [:]) { $0[$1.0] = $1.1 }
}
}
但这假设您不需要支持旧操作系统版本,不熟悉新的并发系统等。如果您有兴趣,请观看 WWDC 2021 视频Meet async/await in Swift、Use async/await with URLSession 等。
但是新的并发系统是两全其美的。它“等待”结果,但仍然是真正的异步模式。