从 Swift 中的 URL 列表下载内容的安全方法 3
Safe way to download content from a list of URL's in Swift 3
我有一个 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()
}
}
}
如果你想同时发出所有三个请求(这很好),那么你将按原样递增 i
(因为这是在 urlList 数组中循环)。
您可能遇到的问题是完成时的线程争用。附加到图像数组不是原子操作,我不知道这些完成是否都发生在同一个线程上。您可以使用只有一个线程为其提供服务的委托队列构建 URLSession —— 这将确保在协调的情况下完成。如果没有,您可能需要将更新同步到 images
。
此外,您正在为每个完成的图像调用一次 fetchImages
completionHandler(不确定您是否想要),而不是在它们全部完成时调用一次。
大中央派遣
这是一个典型的多线程场景,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)
}
}
}
群发
我用这一行创建了一个 GroupDispatch
let group = DispatchGroup()
你可以把它看成一个计数器。每次调用 group.enter()
时,计数器都会增加 1
。每次调用 group.leave()
时,计数器都会减少 1
。
当然 DispatchGroup
是 线程安全的。
最后你写的一切
group.notify(queue: .main) {
// HERE <------
}
会在DispatchGroup
的内部计数器为zero
时执行。事实上,这意味着所有待处理的 "operations" 都已完成。
串行队列
最后,您需要从不同的线程访问 images
数组。这是非常危险的。
所以我们使用串行队列。
serialQueue.async {
images.append(image)
group.leave()
}
我们附加到一个Serial Queue
的所有闭包都被执行了1次。所以 images
数组是安全访问的。
I haven't tested the code, please try and let me know ;)
我有一个 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()
}
}
}
如果你想同时发出所有三个请求(这很好),那么你将按原样递增 i
(因为这是在 urlList 数组中循环)。
您可能遇到的问题是完成时的线程争用。附加到图像数组不是原子操作,我不知道这些完成是否都发生在同一个线程上。您可以使用只有一个线程为其提供服务的委托队列构建 URLSession —— 这将确保在协调的情况下完成。如果没有,您可能需要将更新同步到 images
。
此外,您正在为每个完成的图像调用一次 fetchImages
completionHandler(不确定您是否想要),而不是在它们全部完成时调用一次。
大中央派遣
这是一个典型的多线程场景,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)
}
}
}
群发
我用这一行创建了一个 GroupDispatch
let group = DispatchGroup()
你可以把它看成一个计数器。每次调用 group.enter()
时,计数器都会增加 1
。每次调用 group.leave()
时,计数器都会减少 1
。
当然 DispatchGroup
是 线程安全的。
最后你写的一切
group.notify(queue: .main) {
// HERE <------
}
会在DispatchGroup
的内部计数器为zero
时执行。事实上,这意味着所有待处理的 "operations" 都已完成。
串行队列
最后,您需要从不同的线程访问 images
数组。这是非常危险的。
所以我们使用串行队列。
serialQueue.async {
images.append(image)
group.leave()
}
我们附加到一个Serial Queue
的所有闭包都被执行了1次。所以 images
数组是安全访问的。
I haven't tested the code, please try and let me know ;)