0

So my app only displays images in a CollectionView and it crashes because of memory issues. This is the memory graph Memory Graph

This is the sample project you can check. ImageTest

I've tried this same project with Kingfisher Library and AlamofireImage Library and it crashes on both.

Akash Popat
  • 420
  • 3
  • 18
  • If you try with only ~20 images, do you still run out of memory? I noticed your images are relatively high quality. I'm not so sure though that they're big enough to cause you to run out of memory, but I suppose it's possible if you have enough cached in memory (which is what Kingfisher and AlamofireImage do) – Daniel Aug 15 '18 at 16:30
  • Even with 20 something images I'm having these spikes and crashes though it took longer to crash. The thing I don't get is that doesn't Kingfisher and AlamofireImage already have a default cache limit. Why would I see such spikes and crashes if they do. – Akash Popat Aug 15 '18 at 16:49
  • Does it crash with just 1 or 2 images? Also, you might want to take a look at [this thread for Kingfisher](https://github.com/onevcat/Kingfisher/issues/796). – Daniel Aug 15 '18 at 17:20
  • Doesn't crash with 2-3 images. Reading that thread just makes me think I should switch to PINRemoteImage. I'm surprised more people aren't having this issue with high res images who use AlamofireImage or Kingfisher. – Akash Popat Aug 15 '18 at 17:46
  • Maybe try scaling down your images and see if that helps. Usually you don't need super high res images for mobile apps – Daniel Aug 15 '18 at 17:50
  • Yeah I think that where the crashes are occuring from but I can't find a way by which any library downsamples and then stores an UIImage to cache. It would be I would have to manually downsample each image and also have to code the caching process. Even though both of those would be high level, I doubt everyone has their own implementation of this. – Akash Popat Aug 15 '18 at 17:54
  • Please update your question with a crash log or jetsam log. Jetsam logs are available after a device is synced from iTunes. On your mac they will be within `~/Library/Logs/CrashReporter/MobileDevice/` – quellish Aug 19 '18 at 21:56
  • Why you are not using SDWebImage?, because it don't have this kind of issues. – Dhaval Dobariya Aug 22 '18 at 14:53

1 Answers1

1

It seems likely that the problem is caused by your images being too large. I see two solutions.

PINRemoteImage

Try using PINRemoteImage. It's in ObjC but you can bridge to Swift. This framework allows you to set limits on cache size, which should prevent your memory from being gobbled up.

However, this might not help because you could end up not having all your images.

Scale images down

Because, as you noted, scaling the images one by one is manual (and therefore tedious), I suggest scaling on the client-end.

To do this, you would probably end up writing your own caching code. I've done this before though, and I can attest that it is actually quite simple to get something that meets your needs. For example, when I had to cache images, I ended up creating a dictionary that stores images with url keys. Before you store the images in the dictionary, you scale them down.

As requested, here is some sample code to help you out. This isn't the entire code, but it is a very solid base.

Downloading the image

Use Alamofire to download the image from a URL:

Alamofire.request(.GET, "https://robohash.org/123.png").response { (request, response, data, error) in
    self.myImageView.image = UIImage(data: data, scale:1)
}

Scaling the image

Use the code from this answer on SO. You should scale to the size that you need the image and no more.

Storing the image

Let's back up a bit. I would have all of this code managed by a class, ImageManager, or something like that.

ImageManager should have:

var delegate: ImageManagerDelegate?               // the delegate; more detail below
private(set) var images: [URL: UIImage] = [:]     // the cache

func getImage(from url: URL)                      // the entry point to the class; calls the delegate immediately if the image is already cached, else calls `downloadImage(url: url)`
private func downloadImage(url: URL)              // actually downloads the image; calls `cacheImage(url: url, image: downloadedImage)`
private func cacheImage(url: URL, image: UIImage) // caches the image in `images` with `url` as the key, and notifies `delegate` that an image has been cached with the specified url.

ImageManager should also implement ImageManagerDelegate:

protocol ImageManagerDelegate {
    func imageManager(_ manager: ImageManager, didGet image: UIImage, from url: URL)
}
Daniel
  • 3,188
  • 14
  • 34
  • Can you provide a sample code on how you would integrate downloading, scaling, caching and displaying simply ? – Akash Popat Aug 15 '18 at 18:12
  • @AkashPopat Use Image I/O framework. Watch the WWDC 2018 video on image best practices. – matt Aug 15 '18 at 18:14