我使用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)
}
所以我下载de image并将其存储在字典中。关键是单元格的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
})
}
我做错了什么?感谢。
答案 0 :(得分:1)
有几个问题:
如果您使用NSMutableDictionary
,则必须同步与该词典的所有互动。 NSMutableDictionary
不是线程安全的。如果使用具有非常相似接口的NSCache
,它提供了线程安全交互,因此不需要手动同步。
此外,您不应只更新操作中的cell
。您不知道与该索引路径关联的单元格是否仍然可见(或者更糟糕的是,它是否已被重用于其他索引路径)。您应该使用cellForRow(at:)
(不要与类似名称的UITableViewDataSource
方法混淆)来获取与IndexPath
关联的当前单元格。如果返回非nil
单元格引用,您应该使用它来更新单元格的图像。如果是nil
,则无法更新可见UIImageView
。
与您的崩溃无关,如果您快速滚动查看表格视图,您的网络请求队列可能会积压下载图片,以查看不再可见的单元格。例如,如果快速滚动到第100行,则对可见单元格的请求可以在对不再可见的前99行的请求后面进行积压。如果它们具有低速网络连接(您应该尝试使用“网络链路调节器”进行模拟),则会放大此问题。
这使得更成问题的原因是您正在使用同步的,不可取消的网络请求。如果您(正如EricD所建议的那样)使用UIImageView
扩展之一进行异步图像检索(例如AlamofireImage,Kingfisher等等;那里有许多扩展名,可以减轻这个问题