2

Im developing an image loading library in Swift 4 something like Kingfisher with some extensions to support loading images from URL into an UIImageView .

So then i can use this extension on a UICollection or UITableview cell with an UIImageView like this :

let cell = collectionView.dequeueReusableCell(withReuseIdentifier: 
  "collectioncell", for: indexPath)

    if let normal = cell as? CollectionViewCell {
    normal.imagview.loadImage(fromURL:imageURLstrings[indexPath.row])
  }

Basically this is my extension Code , implementing the basic URLSession dataTask and Caching

public extension UIImageView {
public func loadImage(fromURL url: String) {
    guard let imageURL = URL(string: url) else {
        return
    }

    let cache =  URLCache.shared
    let request = URLRequest(url: imageURL)
    DispatchQueue.global(qos: .userInitiated).async {
        if let data = cache.cachedResponse(for: request)?.data, let 
           image = UIImage(data: data) {
             DispatchQueue.main.async {
                self.image = image
            }
        } else {
            URLSession.shared.dataTask(with: request, 
       completionHandler: { (data, response, error) in

            print(response.debugDescription)
                print(data?.base64EncodedData() as Any)
                print(error.debugDescription)
                if let data = data, let response = response, ((response as? HTTPURLResponse)?.statusCode ?? 500) < 300, let image = UIImage(data: data) {
                    let cachedData = CachedURLResponse(response: response, data: data)
                    cache.storeCachedResponse(cachedData, for: request)
                    DispatchQueue.main.async {
                       self.image = image
                    }
                }
            }).resume()
        }
    }
}



 }

Concluding i found out that sometimes if my URL is broken or i get 404 or even if i scroll the UICollectionView before all the images are completely downloaded, images are loaded into the wrong cell or i sometimes find duplicate images in collectionView but this does not happen if i use Kingfisher.

This is my results

This is kingfishers

How do i prevent my extension from loading the wrong image into a cell or duplicating images?

Rob
  • 415,655
  • 72
  • 787
  • 1,044
BigFire
  • 317
  • 1
  • 4
  • 17
  • @rob can you help me here ? https://stackoverflow.com/questions/57137326/is-there-a-specific-way-to-append-dispatchworkitems-to-a-dispatchqueue-instead-o – BigFire Jul 21 '19 at 22:49

1 Answers1

2

You are asynchronously updating your image view, regardless of whether the image view has been re-used for another cell.

When you start a new request for an image view, assuming you didn’t find an image in the cache immediately, before starting network request, you should (a) remove any prior image (like Brandon suggested); (b) possibly load a placeholder image or UIActivityIndicatorView; and (c) cancel any prior image request for that image view. Only then should you start a new request.

In terms of how you save a reference to the prior request in an extension, you can’t add stored properties, but you can use objc_setAssociatedObject to save the session task when you start the session, set it to nil when the request finishes, and objc_getAssociatedObject when retrieving the session object to see if you need to cancel the prior one.

(Incidentally, Kingfisher wraps this associated object logic in their computed property for the task identifier. This is a fine way to save and retrieve this task identifier.


In terms of failed requests, the fact that you are performing unbridled image requests could cause that problem. Scroll around a bit and your requests will get backlogged and timeout. Doing the cancelation (see above) will diminish that problem, but it might still eventually happen. If you continue to have requests fail after fixing the above, then you might need to constrain the number of concurrent requests. A common solution is to wrap requests in asynchronous Operation subclass and add them to OperationQueue with a maxConcurrentOperationCount. Or if you’re looking for a cheap and cheerful solution, you could try bumping up the timeout threshold in your requests.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Removing any prior image like Brandon suggested was not enough to solve the problem , and i don't know why he deleted his answer ,However i was able to track the session object and cancelled any prior task as you suggested which really solved the problem ,Thank you. – BigFire May 12 '19 at 07:24
  • Brandon’s observation is an essential part of the solution (if you don’t reset the image immediately, you may see the old image temporarily while retrieving the new image), but as you discovered, it is only a small part of the whole solution. – Rob May 12 '19 at 07:28
  • i did click the checkmark and i got this response "Thanks for the feedback! Votes cast by those with less than 15 reputation are recorded, but do not change the publicly displayed post score." – BigFire May 12 '19 at 07:37
  • [link](https://stackoverflow.com/questions/56101946/is-there-a-way-to-request-multiple-distinct-resources-in-parallel-using-urlsessi) @Rob any help with this ? – BigFire May 12 '19 at 17:57
  • @big fire can you please share the code after the problem was fixed because i am facing the similar problem here image duplication or wrong image load – Mohammad Yunus May 13 '19 at 14:27
  • @MohammadYunus , [check this answer](https://stackoverflow.com/questions/56101946/is-there-a-way-to-request-multiple-distinct-resources-in-parallel-using-urlsessi) look for Robs answer . – BigFire May 13 '19 at 14:30