8

I am working with NSCache, I am using NSCache to store images. I am showing images on UITableView. Whenever I add images first they are resized and then added to the table and then to NSCache. eveything works fine.

But whenever app goes to background when I close the app and open again, My Cache will be empty and again my app resizes images and then show it, because of which at first I see a empty table.

I am not understanding why this is happening. Is this the intended behaviour of NSCache? .If yes then how can we improve the user experience so that use doesnt see the lag.

here is my code which was suggested to me by @ipmcc

Here category is my entity name (I am using coreData)

// A shared (i.e. global, but scoped to this function) cache
    static NSCache* imageCache = nil;
    // The following initializes the cache once, and only once
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        imageCache = [[NSCache alloc] init];
    });

    // Generate a cache key sufficient to uniquely identify the image we're looking for
    NSString* cacheKey = [NSString stringWithFormat: @"%@", category.name];
    // Try fetching any existing image for that key from the cache.
    UIImage* img = [imageCache objectForKey: cacheKey];
    self.imageView.image = img;

    // If we don't find a pre-existing one, create one
    if (!img)
    {
        // Your original code for creating a resized image...
        UIImage *image1 = [UIImage imageWithData:category.noteImage];

        CGSize newSize;
        if(image1.size.width == 1080 && image1.size.height == 400)
        {
            newSize = CGSizeMake(300, 111);
        }

        dispatch_async(dispatch_get_global_queue(0,0), ^{
            UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
            [image1 drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
            UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();

            dispatch_async(dispatch_get_main_queue(), ^{
                // Now add the newly-created image to the cache
                [imageCache setObject: newImage forKey: cacheKey];
                self.imageView.image = newImage;
            });
        });
    }
Parth Bhatt
  • 19,381
  • 28
  • 133
  • 216
Ranjit
  • 4,576
  • 11
  • 62
  • 121
  • How are you closing the app and how have you implemented the cache usage (show code). – Wain Dec 16 '13 at 08:19
  • hi @Wain, I have added code in my above question. I am closing the app by pressing the home button. – Ranjit Dec 16 '13 at 08:25
  • Yes, this is the intended behavior of NSCache. If you intend to circumvent it by using NSDictionary you will likely not have an app to come back to at all since you app will be killed in the background due to high memory usage. Don't fight this process. Instead have a smooth transition into your images if they are not available. There are libraries out there for downloading and caching images. NSCache should not be used for images. – borrrden Dec 16 '13 at 08:40
  • hi @borrrden, I am not downloading my images from any server, My images are stored in coreData db , I am just fetching those images from it , resizing it and then displaying it on the table.IF NSCache should not be used for images, then what should be used? – Ranjit Dec 16 '13 at 09:25
  • Why are your images stored there? That is a weird way to do things...instead you should consider storing the image NAME and then you can use `imageNamed:` and get the caching behavior for free. – borrrden Dec 16 '13 at 09:32
  • @borrrden, sorry I meant my entity called Category is stored in db, which has an attribute called image. And I didnt get your solution of just saving Image name and getting caching behaviour for free. – Ranjit Dec 16 '13 at 09:39
  • The normal way to do it would be to save the filename of your image in the DB, and then go to get it with `[UIImage imageNamed:]`. This method will cache the image for you so that the next time you get it, it is from memory instead of disk. It will automatically purge under pressure as it should. – borrrden Dec 16 '13 at 09:41
  • @borrrden, The images are created by user, user creates these images on fly. Infact I am converting whatever user draws into a image. – Ranjit Dec 16 '13 at 09:43
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/43241/discussion-between-ranjit-and-borrrden) – Ranjit Dec 16 '13 at 09:46
  • In that case, I suggest trying this library -> https://github.com/path/FastImageCache – borrrden Dec 16 '13 at 09:46
  • @borrrden, I will look at it and get back to you – Ranjit Dec 16 '13 at 10:08

1 Answers1

16

Yes it does that, for some reason it instantly removes data from cache when app enters background even if there is no memory pressure. To fix this you have to tell NSCache that your data should not be discarded.

Here is how you fix this.

class ImageCache: NSObject , NSDiscardableContent {

    public var image: UIImage!

    func beginContentAccess() -> Bool {
        return true
    }

    func endContentAccess() {

    }

    func discardContentIfPossible() {

    }

    func isContentDiscarded() -> Bool {
        return false
    }
}

and then use this class in NSCache like this.

let cache = NSCache<NSString, ImageCache>()

and then set the data in cache like this

let cacheImage = ImageCache()
cacheImage.image = imageDownloaded
self.cache.setObject(cacheImage, forKey: "somekey" as NSString)

and to retrive the data

if let cachedVersion = cache.object(forKey: "somekey") {
     youImageView.image = cachedVersion.image
}
Sharjeel Ahmad
  • 211
  • 3
  • 7
  • This helped me immensely when populating collectionViews. NSCache also removes items if they are within a reusable cell that is no longer within the visible window. With this new NSObject and a good extension on UIImageView the collection view loads a lot smoother now. – Sanzio Angeli Aug 23 '20 at 19:01