如何使用 UIImageView 扩展以 Swift 异步加载图像并防止重复图像或错误图像加载到单元格
How to load image asynchronously with Swift using UIImageViewExtension and preventing duplicate images or wrong Images loaded to cells
我正在 Swift 4 中开发一个图像加载库,类似于 Kingfisher,带有一些扩展以支持将图像从 URL 加载到 UIImageView 中。
然后我可以在带有 UIImageView 的 UICollection 或 UITableview 单元格上使用此扩展,如下所示:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier:
"collectioncell", for: indexPath)
if let normal = cell as? CollectionViewCell {
normal.imagview.loadImage(fromURL:imageURLstrings[indexPath.row])
}
基本上这是我的扩展代码,实现了基本的URL会话数据任务和缓存
public extension UIImageView {
public func loadImage(fromURL url: String) {
guard let imageURL = URL(string: url) else {
return
}
let cache = URLCache.shared
let request = URLRequest(url: imageURL)
DispatchQueue.global(qos: .userInitiated).async {
if let data = cache.cachedResponse(for: request)?.data, let
image = UIImage(data: data) {
DispatchQueue.main.async {
self.image = image
}
} else {
URLSession.shared.dataTask(with: request,
completionHandler: { (data, response, error) in
print(response.debugDescription)
print(data?.base64EncodedData() as Any)
print(error.debugDescription)
if let data = data, let response = response, ((response as? HTTPURLResponse)?.statusCode ?? 500) < 300, let image = UIImage(data: data) {
let cachedData = CachedURLResponse(response: response, data: data)
cache.storeCachedResponse(cachedData, for: request)
DispatchQueue.main.async {
self.image = image
}
}
}).resume()
}
}
}
}
最后我发现有时如果我的 URL 坏了或者我得到 404 或者即使我在所有图像完全下载之前滚动 UICollectionView,图像加载到错误的单元格或者我有时会发现collectionView 中的重复图像,但如果我使用 Kingfisher,则不会发生这种情况。
This is my results
This is kingfishers
如何防止我的扩展程序将错误的图像加载到单元格或复制图像?
您正在异步更新图像视图,无论图像视图是否已 re-used 另一个单元格。
当您开始新的图像视图请求时,假设您没有立即在缓存中找到图像,那么在开始网络请求之前,您应该 (a) 删除任何先前的图像(如 Brandon 所建议的); (b) 可能加载占位符图像或 UIActivityIndicatorView
; (c) 取消对该图像视图的任何先前图像请求。只有这样你才应该开始一个新的请求。
关于如何在扩展中保存对先前请求的引用,您不能添加存储属性,但您可以在检索会话对象时使用objc_setAssociatedObject
to save the session task when you start the session, set it to nil
when the request finishes, and objc_getAssociatedObject
查看是否需要取消前一个。
(顺便说一下,Kingfisher 在他们的 computed property for the task identifier 中包装了这个关联的对象逻辑。这是保存和检索这个任务标识符的好方法。
就失败的请求而言,您正在执行无节制的图像请求这一事实可能会导致该问题。稍微滚动一下,您的请求将积压并超时。进行取消(见上文)将减少该问题,但它最终可能仍会发生。如果在修复上述问题后请求仍然失败,那么您可能需要限制并发请求的数量。一个常见的解决方案是将请求包装在异步 Operation
子类中,然后使用 maxConcurrentOperationCount
将它们添加到 OperationQueue
。或者,如果您正在寻找一种廉价而愉快的解决方案,您可以尝试提高请求中的超时阈值。
我正在 Swift 4 中开发一个图像加载库,类似于 Kingfisher,带有一些扩展以支持将图像从 URL 加载到 UIImageView 中。
然后我可以在带有 UIImageView 的 UICollection 或 UITableview 单元格上使用此扩展,如下所示:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier:
"collectioncell", for: indexPath)
if let normal = cell as? CollectionViewCell {
normal.imagview.loadImage(fromURL:imageURLstrings[indexPath.row])
}
基本上这是我的扩展代码,实现了基本的URL会话数据任务和缓存
public extension UIImageView {
public func loadImage(fromURL url: String) {
guard let imageURL = URL(string: url) else {
return
}
let cache = URLCache.shared
let request = URLRequest(url: imageURL)
DispatchQueue.global(qos: .userInitiated).async {
if let data = cache.cachedResponse(for: request)?.data, let
image = UIImage(data: data) {
DispatchQueue.main.async {
self.image = image
}
} else {
URLSession.shared.dataTask(with: request,
completionHandler: { (data, response, error) in
print(response.debugDescription)
print(data?.base64EncodedData() as Any)
print(error.debugDescription)
if let data = data, let response = response, ((response as? HTTPURLResponse)?.statusCode ?? 500) < 300, let image = UIImage(data: data) {
let cachedData = CachedURLResponse(response: response, data: data)
cache.storeCachedResponse(cachedData, for: request)
DispatchQueue.main.async {
self.image = image
}
}
}).resume()
}
}
}
}
最后我发现有时如果我的 URL 坏了或者我得到 404 或者即使我在所有图像完全下载之前滚动 UICollectionView,图像加载到错误的单元格或者我有时会发现collectionView 中的重复图像,但如果我使用 Kingfisher,则不会发生这种情况。
This is my results
This is kingfishers
如何防止我的扩展程序将错误的图像加载到单元格或复制图像?
您正在异步更新图像视图,无论图像视图是否已 re-used 另一个单元格。
当您开始新的图像视图请求时,假设您没有立即在缓存中找到图像,那么在开始网络请求之前,您应该 (a) 删除任何先前的图像(如 Brandon 所建议的); (b) 可能加载占位符图像或 UIActivityIndicatorView
; (c) 取消对该图像视图的任何先前图像请求。只有这样你才应该开始一个新的请求。
关于如何在扩展中保存对先前请求的引用,您不能添加存储属性,但您可以在检索会话对象时使用objc_setAssociatedObject
to save the session task when you start the session, set it to nil
when the request finishes, and objc_getAssociatedObject
查看是否需要取消前一个。
(顺便说一下,Kingfisher 在他们的 computed property for the task identifier 中包装了这个关联的对象逻辑。这是保存和检索这个任务标识符的好方法。
就失败的请求而言,您正在执行无节制的图像请求这一事实可能会导致该问题。稍微滚动一下,您的请求将积压并超时。进行取消(见上文)将减少该问题,但它最终可能仍会发生。如果在修复上述问题后请求仍然失败,那么您可能需要限制并发请求的数量。一个常见的解决方案是将请求包装在异步 Operation
子类中,然后使用 maxConcurrentOperationCount
将它们添加到 OperationQueue
。或者,如果您正在寻找一种廉价而愉快的解决方案,您可以尝试提高请求中的超时阈值。