1

Error in the 3rd line due to the declaration of a constant, why is this happening? Error: Extensions must not contain stored properties Code:

extension UIImageView {

    let imageCache = NSCache<NSString, UIImage>() //error
    
        func imageFromServerURL(_ URLString: String, placeHolder: UIImage?) {

        self.image = nil
        //If imageurl's imagename has space then this line going to work for this
        let imageServerUrl = URLString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
        if let cachedImage = imageCache.object(forKey: NSString(string: imageServerUrl)) {
            self.image = cachedImage
            return
        }

        if let url = URL(string: imageServerUrl) {
            URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in

                //print("RESPONSE FROM API: \(response)")
                if error != nil {
                    print("ERROR LOADING IMAGES FROM URL: \(String(describing: error))")
                    DispatchQueue.main.async {
                        self.image = placeHolder
                    }
                    return
                }
                DispatchQueue.main.async {
                    if let data = data {
                        if let downloadedImage = UIImage(data: data) {
                            self.imageCache.setObject(downloadedImage, forKey: NSString(string: imageServerUrl))
                            self.image = downloadedImage
                        }
                    }
                }
            }).resume()
        }
    }
}

I used this code and don't understand why it doesn't work: Swift async load image

TheMFA
  • 13
  • 3

1 Answers1

0

Yes, extensions may not add stored properties. It is probably best this way so that your classes do not increase in size due to libraries that extend your classes by adding some additional information which is most likely not interesting for you.

But in your case you probably don't even want to have a stored property. The way you coded it means that each of your UIImageView instance will have its own cache. So for instance a table view showing an image view per cell means a cache for each visible cell which results in multiple downloading of the same image instead of sharing it.

Probably the best solution to this problem in your case is to make it static:

extension UIImageView {

    private static let imageCache = NSCache<NSString, UIImage>()
    private var imageCache: NSCache<NSString, UIImage> { UIImageView.imageCache }
    
    func imageFromServerURL(_ URLString: String, placeHolder: UIImage?) {
        self.image = nil
        // If image url's image name has space then this line going to work for this
        let imageServerUrl = URLString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? ""
        if let cachedImage = imageCache.object(forKey: NSString(string: imageServerUrl)) {
            self.image = cachedImage
            return
        }

        if let url = URL(string: imageServerUrl) {
            URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
                //print("RESPONSE FROM API: \(response)")
                if error != nil {
                    print("ERROR LOADING IMAGES FROM URL: \(String(describing: error))")
                    DispatchQueue.main.async {
                        self.image = placeHolder
                    }
                    return
                }
                DispatchQueue.main.async {
                    if let data = data {
                        if let downloadedImage = UIImage(data: data) {
                            self.imageCache.setObject(downloadedImage, forKey: NSString(string: imageServerUrl))
                            self.image = downloadedImage
                        }
                    }
                }
            }).resume()
        }
    }
    
}

Aside your question you should know that the task you are trying to solve may not be as easy as the code you provided. There are at least 2 major problems I see in your code:

  1. When calling this method multiple times quickly you may have a race condition where first call will download the image later than the second one. In this case the first image will be shown instead of the second (last) one
  2. When calling this method multiple times for same image you will initiate download on the same resource multiple times (Same image is shown on 2 locations of the screen will download it twice)

And then there are still possibly many more. For instance, this is all in memory. How many images do you expect you can cache before your memory is depleted? Maybe best to move this into some file system.

Matic Oblak
  • 16,318
  • 3
  • 24
  • 43
  • Why would you create an instance property to access a static one? IMO this is very misleading considering that they all point to the same instance. – Leo Dabus Mar 29 '21 at 14:28
  • @LeoDabus This is pretty much a standard practice. It is not needed as you could call UIImageView.imageCache everywhere instead of just imageCache. But then again... you need to call UIImageView.imageCache everywhere. Also in the long run this would probably be: private var imageCache: FileCache { .shared } – Matic Oblak Mar 29 '21 at 15:19