UITableViewCell异步加载图像问题-Swift

UITableViewCell异步加载图像问题-Swift,swift,uitableview,asynchronous,uiimageview,Swift,Uitableview,Asynchronous,Uiimageview,在我的应用程序中,我构建了自己的异步图像加载类。我传入一个对象,然后它检查缓存(NSCache)是否有图像,如果没有,它将检查文件系统是否已经保存了图像。如果图像尚未保存,它将在后台下载图像(NSOperations帮助) 到目前为止,这非常有效,但我在加载图像的表视图时遇到了一些小问题 首先,这是我用来从tableView(tableView:,willDisplayCell:,forrowatinexpath:)设置表视图单元格的函数。 “//问题在这里”的评论是,这是我遇到多个问题的地方

在我的应用程序中,我构建了自己的异步图像加载类。我传入一个对象,然后它检查缓存(NSCache)是否有图像,如果没有,它将检查文件系统是否已经保存了图像。如果图像尚未保存,它将在后台下载图像(NSOperations帮助)

到目前为止,这非常有效,但我在加载图像的表视图时遇到了一些小问题

首先,这是我用来从
tableView(tableView:,willDisplayCell:,forrowatinexpath:)设置表视图单元格的函数。

“//问题在这里”的评论是,这是我遇到多个问题的地方

到目前为止,我还没有找到另一种方法来验证图像是否属于单元格,以确定“//问题在哪里”。如果我加上

cell.backgroundImage=图像

然后,它修复了有时图像不会显示在表视图单元格上的问题。到目前为止,我发现的唯一原因是,返回图像的速度比返回表视图单元格的速度快,这就是为什么表视图说该索引路径上没有单元格的原因

但是如果我在那里添加代码,那么我会遇到另一个问题!细胞会显示错误的图像,然后它会延迟应用程序,图像会不断切换,甚至只是停留在错误的图像上

我已经检查过它在主线程上运行,图像下载和缓存都很好。它只需要这样做,即表表示在该索引路径上没有单元格,我已经尝试为该单元格获取一个indexPath,它也返回nil

此问题的半解决方案称为tableView.reloadData(),位于ViewWillDisplay/ViewDidDisplay中。这将解决问题,但随后我会丢失屏幕上表格视图单元格的动画

编辑:


如果我将图像视图传递到getImageForShow()并直接设置它,它将解决此问题,但这不是理想的代码设计。图像视图显然存在,单元格也存在,但由于某些原因,它不想每次都工作

表视图重用单元格以节省内存,这可能会导致显示单元格数据所需执行的任何异步例程出现问题(如加载图像)。如果异步操作完成时单元格应该显示不同的数据,则应用程序可能会突然进入不一致的显示状态

为了解决这个问题,我建议在单元格中添加一个generation属性,并在异步操作完成时检查该属性:

protocol MyImageManager {
    static var sharedManager: MyImageManager { get }
    func getImageForUrl(url: String, completion: (UIImage?, NSError?) -> Void)
}

struct MyCellData {
    let url: String
}

class MyTableViewCell: UITableViewCell {

    // The generation will tell us which iteration of the cell we're working with
    var generation: Int = 0

    override func prepareForReuse() {
        super.prepareForReuse()
        // Increment the generation when the cell is recycled
        self.generation++
        self.data = nil
    }

    var data: MyCellData? {
        didSet {
            // Reset the display state
            self.imageView?.image = nil
            self.imageView?.alpha = 0
            if let data = self.data {
                // Remember what generation the cell is on
                var generation = self.generation
                // In case the image retrieval takes a long time and the cell should be destroyed because the user navigates away, make a weak reference
                weak var wcell = self
                // Retrieve the image from the server (or from the local cache)
                MyImageManager.sharedManager.getImageForUrl(data.url, completion: { (image, error) -> Void in
                    if let error = error {
                        println("There was a problem fetching the image")
                    } else if let cell = wcell, image = image where cell.generation == generation {
                        // Make sure that UI updates happen on main thread
                        dispatch_async(dispatch_get_main_queue(), { () -> Void in
                            // Only update the cell if the generation value matches what it was prior to fetching the image
                            cell.imageView?.image = image
                            cell.imageView?.alpha = 0
                            UIView.animateWithDuration(0.25, animations: { () -> Void in
                                cell.imageView?.alpha = 1
                            })
                        })
                    }
                })
            }
        }
    }
}

class MyTableViewController: UITableViewController {

    var rows: [MyCellData] = []

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCellWithIdentifier("Identifier") as! MyTableViewCell
        cell.data = self.rows[indexPath.row]
        return cell
    }

}
还有几点需要注意:

  • 不要忘记在主线程上进行显示更新。在网络活动线程上更新可能会导致显示在看似随机的时间更改(或从不更改)
  • 在执行异步操作时,请确保弱引用单元格(或任何其他UI元素),以防在异步操作完成之前销毁UI

我做的有点不同,但generation变量起作用。我将我的configureCell代码保留在原来的位置,只需对照那里的生成进行检查,根据我所知道的,图像现在总是正确的。谢谢:)这有一个问题,即当滚动回顶部时图像设置不正确,因为您总是在prepareForReuse中增加生成值。您可以使用tag属性而不是生成。
protocol MyImageManager {
    static var sharedManager: MyImageManager { get }
    func getImageForUrl(url: String, completion: (UIImage?, NSError?) -> Void)
}

struct MyCellData {
    let url: String
}

class MyTableViewCell: UITableViewCell {

    // The generation will tell us which iteration of the cell we're working with
    var generation: Int = 0

    override func prepareForReuse() {
        super.prepareForReuse()
        // Increment the generation when the cell is recycled
        self.generation++
        self.data = nil
    }

    var data: MyCellData? {
        didSet {
            // Reset the display state
            self.imageView?.image = nil
            self.imageView?.alpha = 0
            if let data = self.data {
                // Remember what generation the cell is on
                var generation = self.generation
                // In case the image retrieval takes a long time and the cell should be destroyed because the user navigates away, make a weak reference
                weak var wcell = self
                // Retrieve the image from the server (or from the local cache)
                MyImageManager.sharedManager.getImageForUrl(data.url, completion: { (image, error) -> Void in
                    if let error = error {
                        println("There was a problem fetching the image")
                    } else if let cell = wcell, image = image where cell.generation == generation {
                        // Make sure that UI updates happen on main thread
                        dispatch_async(dispatch_get_main_queue(), { () -> Void in
                            // Only update the cell if the generation value matches what it was prior to fetching the image
                            cell.imageView?.image = image
                            cell.imageView?.alpha = 0
                            UIView.animateWithDuration(0.25, animations: { () -> Void in
                                cell.imageView?.alpha = 1
                            })
                        })
                    }
                })
            }
        }
    }
}

class MyTableViewController: UITableViewController {

    var rows: [MyCellData] = []

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCellWithIdentifier("Identifier") as! MyTableViewCell
        cell.data = self.rows[indexPath.row]
        return cell
    }

}