用于将图像加载到 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
    })
}

我做错了什么?谢谢

几个问题:

  1. 如果您使用 NSMutableDictionary,您必须同步所有与该词典的交互。 NSMutableDictionary 不是线程安全的。如果您使用 NSCache,它具有非常相似的界面,它提供线程安全的交互,因此不需要手动同步。

  2. 此外,您不应该只在操作中更新 cell。您不知道与该索引路径关联的单元格是否仍然可见(或者更糟的是,它是否已被其他索引路径重用)。您应该使用 cellForRow(at:)(不要与名称相似的 UITableViewDataSource 方法混淆)来获取与 IndexPath 关联的当前单元格。如果 returns 一个非 nil 单元格引用,您是否应该使用它来更新单元格的图像。如果是nil,则没有可见的UIImageView要更新。

  3. 与您的崩溃无关,如果您快速滚动 table 视图,您的网络请求队列可能会积压下载不再可见的单元格的图像。例如,如果您快速滚动到第 100 行,对可见单元格的请求可能会积压在对不再可见的前 99 行的请求之后。如果他们有低速网络连接(你应该尝试用“网络 link 调节器”模拟),这个问题会被放大。

    问题更严重的是您使用的是同步的、不可取消的网络请求。如果您像 EricD 所建议的那样,使用 UIImageView 扩展之一进行异步图像检索(例如 AlamofireImage, Kingfisher 等;那里有很多),则可以缓解此问题。