3 loops asynchronous while-loop completionhandler swift
我有一个提供图像资源位置的 URL 列表。我设法找到了解决方案,但我觉得似乎有比下面代码中显示的过程更好的方法。非常感谢有关如何改进此方法以异步检索图像的任何提示!
在我附加每个项目之后调用completionHandler,并在异步块之外将1添加到索引(i),这意味着while循环在最后一个url被完全处理之前迭代到下一个url项目,这不是很奇怪吗?
typealias imagesHandler = (_ images: [UIImage]) -> Void
func fetchImages(forUser user: User, completionHandler: imagesHandler?) {
var images = [UIImage]()
self.fetchImageUrlList(forUser: user) { (urlList: [URL]) in
var i = 0
while i <= urlList.count - 1 {
let request = URLRequest(url: urlList[i])
let dataTask = URLSession(configuration: .default).dataTask(with: request, completionHandler: {
(data: Data?, response: URLResponse?, error: Error?) in
guard error == nil else { return }
guard let data = data else { return }
guard let image = UIImage(data: data) else { return }
images.append(image)
completionHandler?(Array(Set(images)))
})
i += 1
dataTask.resume()
}
}
}
Run Code Online (Sandbox Code Playgroud)
这是一个典型的多线程场景,Grand Central Dispatch 非常有帮助。
首先,这是您更新的代码,以便使用 Grand Central Dispatch
func fetchImages(forUser user: User, completionHandler: ImagesHandler?) {
var images = [UIImage]()
let group = DispatchGroup()
let serialQueue = DispatchQueue(label: "serialQueue")
fetchImageUrlList(forUser: user) { urls in
urls.forEach { url in
// ***********************************************
// tells the group that there is a pending process
group.enter()
URLSession(configuration: .default).dataTask(with: url) { (data, response, error) in
guard let data = data, let image = UIImage(data: data), error == nil else { group.leave(); return }
// ***************************************************************************
// creates a synchronized access to the images array
serialQueue.async {
images.append(image)
// ****************************************************
// tells the group a pending process has been completed
group.leave()
}
}.resume()
}
group.notify(queue: .main) {
// *****************************************************************************************
// this will be executed when for each group.enter() call, a group.leave() has been executed
completionHandler?(images)
}
}
}
Run Code Online (Sandbox Code Playgroud)
用这条线我创建了一个GroupDispatch
let group = DispatchGroup()
Run Code Online (Sandbox Code Playgroud)
您可以将其视为计数器。每次调用group.enter()计数器都会增加1。每次调用group.leave()计数器都会减少1。
当然DispatchGroup是线程安全的。
最后你写的一切
group.notify(queue: .main) {
// HERE <------
}
Run Code Online (Sandbox Code Playgroud)
当 的内部计数器为 时将被DispatchGroup执行zero。事实上,这意味着所有待处理的“操作”都已完成。
最后,您需要images从不同的线程访问该数组。这是非常危险的。
所以我们使用串行队列。
serialQueue.async {
images.append(image)
group.leave()
}
Run Code Online (Sandbox Code Playgroud)
我们附加到 a 的所有闭包Serial Queue当时都被执行 1。所以images数组是安全访问的。
我还没有测试过代码,请尝试告诉我;)