NSCache Doesn't work with all images when load

2019-01-23 21:47发布

问题:

I'm woking on a project in swift 3.0 where I cache the response from the server by using NSCache as to populate them in a UITableView. However for some reason I'm only seeing few images loading when the app loads for the first time, but if If i scroll and come back I see everything (end of retrieving the response from the server I reload my tableview too, but seems that not the case). I'm not sure what I''m exactly missing here, the code as bellow as to show how I cache the images.

let imageCache = NSCache<AnyObject, AnyObject>()
var imageURLString : String?

extension UIImageView {


    public func imageFromServerURL(urlString: String) {
        imageURLString = urlString

        if let url = URL(string: urlString) {

            image = nil


            if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage {

                self.image = imageFromCache

                return
            }

            URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in

                if error != nil{
                    print(error as Any)


                    return
                }

                DispatchQueue.main.async(execute: {

                    if let imgaeToCache = UIImage(data: data!){

                        if imageURLString == urlString {
                            self.image = imgaeToCache
                        }

                        imageCache.setObject(imgaeToCache, forKey: urlString as AnyObject)// calls when scrolling
                    }
                })
            }) .resume()
        }
    }
}

回答1:

Here the images are downloading and stored in cache just fine. The problem lies in the updation of tableview cells.

When the table view is loading the cells on to the table the images are not downloaded yet. But once the image is downloaded we have to selectively update the cell so that the image is displayed instantly.

Since you are scrolling , the tableview calls 'cellForRowatIndexpath' again which updates the cell showing the downloaded images while scrolling.

If you still wish to use the extension , I suggest you add the tableView and indexpath as the parameters so that we can call reload specific row and have the view updated instantly.

I have updated the table reload code and structure of the function defined in extension. Let me know how it goes.

let imageCache = NSCache<AnyObject, AnyObject>()
var imageURLString : String?

extension UIImageView {


public func imageFromServerURL(urlString: String, tableView : UITableView, indexpath : IndexPath)) {
    imageURLString = urlString

    if let url = URL(string: urlString) {

        image = nil


        if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage {

            self.image = imageFromCache

            return
        }

        URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in

            if error != nil{
                print(error as Any)


                return
            }

            DispatchQueue.main.async(execute: {

                if let imgaeToCache = UIImage(data: data!){

                    if imageURLString == urlString {
                        self.image = imgaeToCache
                    }

                    imageCache.setObject(imgaeToCache, forKey: urlString as AnyObject)// calls when scrolling

    tableView.reloadRows(at: [indexpath], with: .automatic)

                }
            })
        }) .resume()
    }
}


回答2:

I think this would be a better approach using subclassing rather than extension, (taking help from Jageen's comment, as we cannot contain stored properties inside extension so we use the idea of encapsulation)

let imageCache = NSCache<AnyObject, AnyObject>()


    class CustomImageView: UIImageView {

            var imageUrlString: String?
            func loadImageUsingUrlString(_ urlString: String) {
                    let url = URL(string: urlString)
                    imageUrlString = urlString
                    image = nil

                    if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
                            self.image = imageFromCache
                            return
                    }

                    URLSession.shared.dataTask(with: url!) { (data, response, error) in
                            if error != nil {
                                    print(error!)
                                    return

                            }

                            DispatchQueue.main.async {

                                    let imageToCache = UIImage(data: data!)
                                    if self.imageUrlString == urlString {
                                           self.image = imageToCache  
                                    }
                                    imageCache.setObject(imageToCache!, forKey: urlString as AnyObject)

                            }

                            }.resume()

            }
    }

-Now use this subclass as the type of imageViews that you are showing on the screen