4

I'm currently working through my app, updating it to work with Swift 3 and have one problem left. Previously, my image caching worked really well, but since the update the UIImageViews aren't being populated when the image is fetched.

Here is the code (in the ...cellForItemAt... function):

if let img = imageCache[imageUrl] {
    print("CACHE HIT: \(indexPath)")
    cell.image.image = img
} else {
    print("CACHE MISS: \(indexPath)")
    var imgUrl: = URL(string: imageUrl)
    let request: URLRequest = URLRequest(url: imgUrl!)
    let dataTask = session.dataTask(with: request, completionHandler: { (data, response, error) -> Void in
        if(data != nil) {
            print("IMAGE DOWNLOADED: \(imgUrl?.query))")
            let image = UIImage(data: data!) // create the image from the data
            self.imageCache[imageUrl] = image // Store in the cache
            DispatchQueue.main.async(execute: {
                if let cell = collectionView.cellForItem(at: indexPath) as? MyCollectionViewCell {
                    print("APPLYING IMAGE TO CELL: \(indexPath)")
                    cell.image.image = image
                    self.collectionView.reloadData()
                } else {
                    print("CELL NOT FOUND: \(indexPath)")
                }
            })
        } 
    }) 
    dataTask.resume()
}

As you can see, I've added in some prints to find out what is going on. When the view loads, I can see cache misses, and the images loading and the UIImageViews being populated for the visible rows, however when I scroll down, the UIImageViews are never populated, and the log shows CELL NOT FOUND: [0, x] for each indexPath.

If I scroll up again after scrolling down, images are populated from the cache as expected.

The code hasn't changed since the previous version of Swift / iOS / Xcode, and used to work perfectly.

I'm not interested in any 3rd party extensions etc.; I want to understand what is wrong with the code above. Any other improvements/suggestions to the code however are welcome.

Any ideas would be very much appreciated!

Ben
  • 4,707
  • 5
  • 34
  • 55

1 Answers1

2

Rather than getting the cell via the cellForItem(at: indexPath) method, just use the cell variable you use to set the image in the cache hit. This will create a strong reference to the cell's image view.

DispatchQueue.main.async(execute: { [weak collectionView] in
    cell.image.image = image
    collectionView?.reloadData()
})

Consider renaming your UIImageView to imageView rather than image.

Callam
  • 11,409
  • 2
  • 34
  • 32
  • Thanks Callam, that worked perfectly! How does the `[weak self] in` help? – Ben Sep 18 '16 at 19:37
  • 1
    If you reference self in a closure, this creates a strong reference and this can cause problems sometimes. So the `[weak self]` creates a weak reference that will die quietly. – Callam Sep 18 '16 at 19:40
  • 1
    Also, replace `[weak self]` with `[weak collectionView]`, then you can use `collectionView?.reloadData()` in your closure – Callam Sep 18 '16 at 19:44
  • Worked perfectly - the link was very useful too. Many thanks! – Ben Sep 18 '16 at 19:46