I have a UICollectionView
, but the same methods should apply to UITableViews
. Each of my cells contains an image I load from disk, which is a slow operation. To mitigate this, I use an async dispatch queue. This works fine, but quickly scrolling results in these operations stacking up, such that the cell will change its image from one to another in sequence, until finally stopping at the last call.
In the past, I've done a check to see if the cell was still visible, and if not I don't continue. However, this is not working with UICollectionView, and anyway it is not efficient. I am considering migrating to use NSOperations
, which can be cancelled, so that only the last call to modify the cell will go through. I could possibly do this by checking if the operation had finished in the prepareForReuse
method, and cancelling it if not. I'm hoping someone has dealt with this issue in the past and can provide some tips or a solution.
Session 211 - Building Concurrent User Interfaces on iOS, from WWDC 2012, discusses the problem of the cell's image changing as the background tasks catch up (starting at 38m15s).
Here's how you solve that problem. After you've loaded the image on the background queue, use the index path to find the current cell, if any, that's displaying the item containing that image. If you get a cell, set the cell's image. If you get nil, there's no cell currently displaying that item so just discard the image.
Here's an easy way to reduce the "stacking up" of background tasks loading images that the view no longer needs. Give yourself an
NSMutableSet
instance variable:When you need to load an image, put the index path of the cell in the set and dispatch a task that takes one index path out of the set and loads its image:
If a cell stops being displayed, remove the cell's index path from the set:
To load an image, get an index path from the set. We have to dispatch back to the main queue for this, to avoid a race condition on the set:
The
bg_loadImageForRowAtIndexPath:
method is the same as above. But when we actually set the image in the cell, we also want to remove the cell's index path from the set:I had the same problem with an UITableView of about 400 elements and find a brutal but working solution: don't reuse cells! Depends on how much and how big the images are, but actually iOS is quite efficient in deallocating unused cells... Again: is not what purist will suggest, but couple with async load is lighting quick, error free and don't have to deal with complex sync logic.
Have you tried using SDWebImage open source framework that works with downloading images from the web and caching them asynchronously? It works absolutely great with
UITableViews
and should be as good withUICollectionViews
. You just use thesetImage:withURL:
method of theUIImageView
extension and it does magic.