用于将图像加载到 Cell 的 NSOperation - 崩溃
NSOperation for loading image to Cell - crashing
我正在使用 NSOperation 下载每个单元格的图像,这样我就不会因为大量图像加载而使用户超载。这在 99% 的情况下都有效,但有时我在我的操作块中得到一个 nil 并且应用程序崩溃。
这是代码:
cell.blockImage.image = nil
cell.queue.cancelAllOperations()
let cacheKey = indexPath.row
if(self.imagesDictionary.object(forKey: cacheKey) != nil)
{
cell.blockImage.image = self.imagesDictionary.object(forKey: cacheKey) as? UIImage
}
else
{
let operation: BlockOperation = BlockOperation()
operation.addExecutionBlock({
if let url = NSURL(string: self.arrJSONData[indexPath.row].image) {
if let data = NSData(contentsOf: url as URL) {
if let image: UIImage = UIImage(data: data as Data)
{
self.imagesDictionary.setObject(image, forKey: cacheKey as NSCopying)
DispatchQueue.main.async(execute: {
if(operation.isCancelled)
{
return
}
cell.blockImage.image = image
})
}
}
}
})
cell.queue.addOperation(operation)
}
所以我下载图像并将其存储在字典中。关键是单元格的indexPath.row。
我还验证了图像是否已经在字典中,所以我不再下载它。
而且我总是在开始加载单元格时将图像设置为 nil 并取消 BlockOperation。
我经常得到的错误是这样的:
malloc: *** error for object 0x608000244c50: Invalid pointer dequeued from free list
*** set a breakpoint in malloc_error_break to debug
而且我总是把它放在这个块内(但线总是随机的)
if let image: UIImage = UIImage(data: data as Data)
{
self.imagesDictionary.setObject(image, forKey: cacheKey as NSCopying)
DispatchQueue.main.async(execute: {
if(operation.isCancelled)
{
return
}
cell.blockImage.image = image
})
}
我做错了什么?谢谢
几个问题:
如果您使用 NSMutableDictionary
,您必须同步所有与该词典的交互。 NSMutableDictionary
不是线程安全的。如果您使用 NSCache
,它具有非常相似的界面,它提供线程安全的交互,因此不需要手动同步。
此外,您不应该只在操作中更新 cell
。您不知道与该索引路径关联的单元格是否仍然可见(或者更糟的是,它是否已被其他索引路径重用)。您应该使用 cellForRow(at:)
(不要与名称相似的 UITableViewDataSource
方法混淆)来获取与 IndexPath
关联的当前单元格。如果 returns 一个非 nil
单元格引用,您是否应该使用它来更新单元格的图像。如果是nil
,则没有可见的UIImageView
要更新。
与您的崩溃无关,如果您快速滚动 table 视图,您的网络请求队列可能会积压下载不再可见的单元格的图像。例如,如果您快速滚动到第 100 行,对可见单元格的请求可能会积压在对不再可见的前 99 行的请求之后。如果他们有低速网络连接(你应该尝试用“网络 link 调节器”模拟),这个问题会被放大。
问题更严重的是您使用的是同步的、不可取消的网络请求。如果您像 EricD 所建议的那样,使用 UIImageView
扩展之一进行异步图像检索(例如 AlamofireImage, Kingfisher 等;那里有很多),则可以缓解此问题。
我正在使用 NSOperation 下载每个单元格的图像,这样我就不会因为大量图像加载而使用户超载。这在 99% 的情况下都有效,但有时我在我的操作块中得到一个 nil 并且应用程序崩溃。
这是代码:
cell.blockImage.image = nil
cell.queue.cancelAllOperations()
let cacheKey = indexPath.row
if(self.imagesDictionary.object(forKey: cacheKey) != nil)
{
cell.blockImage.image = self.imagesDictionary.object(forKey: cacheKey) as? UIImage
}
else
{
let operation: BlockOperation = BlockOperation()
operation.addExecutionBlock({
if let url = NSURL(string: self.arrJSONData[indexPath.row].image) {
if let data = NSData(contentsOf: url as URL) {
if let image: UIImage = UIImage(data: data as Data)
{
self.imagesDictionary.setObject(image, forKey: cacheKey as NSCopying)
DispatchQueue.main.async(execute: {
if(operation.isCancelled)
{
return
}
cell.blockImage.image = image
})
}
}
}
})
cell.queue.addOperation(operation)
}
所以我下载图像并将其存储在字典中。关键是单元格的indexPath.row。
我还验证了图像是否已经在字典中,所以我不再下载它。
而且我总是在开始加载单元格时将图像设置为 nil 并取消 BlockOperation。
我经常得到的错误是这样的:
malloc: *** error for object 0x608000244c50: Invalid pointer dequeued from free list
*** set a breakpoint in malloc_error_break to debug
而且我总是把它放在这个块内(但线总是随机的)
if let image: UIImage = UIImage(data: data as Data)
{
self.imagesDictionary.setObject(image, forKey: cacheKey as NSCopying)
DispatchQueue.main.async(execute: {
if(operation.isCancelled)
{
return
}
cell.blockImage.image = image
})
}
我做错了什么?谢谢
几个问题:
如果您使用
NSMutableDictionary
,您必须同步所有与该词典的交互。NSMutableDictionary
不是线程安全的。如果您使用NSCache
,它具有非常相似的界面,它提供线程安全的交互,因此不需要手动同步。此外,您不应该只在操作中更新
cell
。您不知道与该索引路径关联的单元格是否仍然可见(或者更糟的是,它是否已被其他索引路径重用)。您应该使用cellForRow(at:)
(不要与名称相似的UITableViewDataSource
方法混淆)来获取与IndexPath
关联的当前单元格。如果 returns 一个非nil
单元格引用,您是否应该使用它来更新单元格的图像。如果是nil
,则没有可见的UIImageView
要更新。与您的崩溃无关,如果您快速滚动 table 视图,您的网络请求队列可能会积压下载不再可见的单元格的图像。例如,如果您快速滚动到第 100 行,对可见单元格的请求可能会积压在对不再可见的前 99 行的请求之后。如果他们有低速网络连接(你应该尝试用“网络 link 调节器”模拟),这个问题会被放大。
问题更严重的是您使用的是同步的、不可取消的网络请求。如果您像 EricD 所建议的那样,使用
UIImageView
扩展之一进行异步图像检索(例如 AlamofireImage, Kingfisher 等;那里有很多),则可以缓解此问题。