0

I'm noticing heavy memory usage from my image cache in my collection view and need to understand how to release it. I understand the difference between UIImage(named:) and UIImage(contentsOfFile:). However, I'm using UIImage(data:) and I can't seem to find any documentation on releasing image caches in this instance. Any help appreciated. Here's my code snippet:

if let setImage = cell?.viewWithTag(101) as? UIImageView {
    if let url = URL(string: imageURLs[indexPath.item]) {
        let task = URLSession.shared.dataTask(with: url, completionHandler: { data, _, error in
            guard let data = data, error == nil else {
                print("No data detected: \(Error.self)")
                return
            }
            DispatchQueue.main.async {
                let newImageData = UIImage(data: data)

                self.imageData[indexPath.item] = newImageData!
                setImage.image = self.imageData[indexPath.item] as? UIImage
            }
        })
        task.resume()
        URLSession.shared.finishTasksAndInvalidate()
    }
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Could you show us how you are creating the cells? Are you using the `dequeueResusableCell` method? – TheAppMentor Aug 13 '20 at 17:39
  • By the way, there's no point in calling `finishTasksAndInvalidate` on the shared session. As [the docs](https://developer.apple.com/documentation/foundation/urlsession/1407428-finishtasksandinvalidate) say, “Calling this method on the session returned by the [`shared`](https://developer.apple.com/documentation/foundation/urlsession/1409000-shared) method has no effect.”. – Rob Aug 13 '20 at 19:13
  • @TheAppMentor Yes. I'm using the dequeueReusableCell method. I'm also using the prepareForReuse method on my cells. – ConanTheLibrarian Aug 13 '20 at 21:18
  • @Rob Understood. I left that there because I just wanted to try it and see what happens. It's superfluous to the issue I'm dealing with. – ConanTheLibrarian Aug 13 '20 at 21:18

2 Answers2

1

UIImage(data:) doesn’t store in the system image cache. So, if you remove all of the references to the associated images from your imageData, make sure the image views are getting released, etc., you should be good.

If imageData a simple collection, consider making it a NSCache, where you can constrain the total count or cost. Also, we often employ two tier caching mechanisms (with smaller in-memory limits, larger persistent storage caches).

You might consider using one of the many third party libraries (AlamofireImage, KingFisher, SDWebImage, etc.) as they tend to employ decent caching strategies, getting you out of the weeds of this. They all offer nice “asynchronous image” extensions to UIImageView, too. (For example, the implementation you’ve shared with us is going to suffer from backlogging issues if you scroll quickly through a big collection view, something that is easily tackled with these UIImageView extensions.) Your UICollectionViewDataSource really should not be burdened with this sort of code.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Could you give me an example as to how to release the references associated with my imageData? I'm looking into using NSCache or any of the libraries you mentioned. Will see if that helps. Whether or not the cache is the issue is just an educated guess on my part. Basically, what is happening is whenever the collectionview reloads, the memory impact keeps increasing and never goes back down. When I comment out the code I shared, the issue disappears. – ConanTheLibrarian Aug 13 '20 at 21:26
  • Set it to `nil` if it is optional. Or call `removeAll`. If it’s dictionary set it to `[:]`. If it is array, set it to `[]`. You haven’t shown enough to answer the question... – Rob Aug 14 '20 at 01:37
  • Are there specifics I should share in the code that would help suss this out? – ConanTheLibrarian Aug 14 '20 at 02:52
  • The declaration of `imageData`. Is it a dictionary? Are you really asking how to remove a single item from a dictionary? Are you asking how to remove everything from a dictionary? – Rob Aug 14 '20 at 03:31
  • It's an array. I've already tried releasing the reference by setting the array to [ ] and the images to nil whenever I leave the viewController. However, the memory usage continues to go up anytime I go back to the viewcontroller that loads the collection view. This tells me that something is staying in memory that has to do with that code snippet that I can't suss out. – ConanTheLibrarian Aug 14 '20 at 04:03
  • I'm also already using Firestore cache and even disconnected the network to only draw from the cache. The memory uptick is tied to the code block because when I comment it out, the memory stays stable. Unless there's something else I'm missing. – ConanTheLibrarian Aug 14 '20 at 04:05
  • I suppose I failed to mention that the code block is sitting within the collection view cellForItemAt method if that helps. – ConanTheLibrarian Aug 14 '20 at 04:28
  • I think I figured out what the culprit is. It seems that Firestore auto-caching only applies to Database documents and not Storage images. I'll just need to setup local caching, along with releasing references and that should theoretically solve my memory issue. Theoretically lol – ConanTheLibrarian Aug 14 '20 at 06:31
0

I was under the impression that Firestore auto-caching applied to cloud Storage, but it only applies to cloud Database. Once I implemented local caching with NSCache, my problem was solved.