0

I have developed a project, where a user draws a image on a canvas, I store it in the file using CoreData, I have one-to-many relationship called folder-to-files. So here all are images. I retrive the images from files , resize according to my table cell height and show it on a table. Once it is shown, I want to cache the images.

I also have some labels on the folder cell, which give me some info regarding my files, which I update on fly. I also swipe the cells to mark it complete and move it to the bottom the cell.

I also show same file images in different Views depending on how user queries it.

I want to know the best method for this, I read through the web, their are many methods, GCD, NSOperationQueues and many more.

Which method will be best suited for me.

I want to show some code

- (UITableViewCell *)tableView:(FMMoveTableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    FolderCell *tableCell = (FolderCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (tableCell == nil)
    {
        NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"FolderCell" owner:self options:nil];
        tableCell = [nib objectAtIndex:0];
    }

    NSMutableArray *categoryArray = [[self.controller fetchedObjects]mutableCopy];

    Folder *category = [categoryArray objectAtIndex:[indexPath row]];

    [tableCell configureCellWithNote:category]; //This function is written in my FolderCell.m function        

}
    return tableCell; 
}


-(void)configureCellWithNote:(Folder *)category
{    
    self.category = category;


    UIImage *image1 = [UIImage imageWithData:category.noteImage];
    CGSize newSize;


    if(image1.size.width == 620 && image1.size.height == 200)
   {              
       newSize = CGSizeMake(300, 97);
   }     

    UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);

    [image1 drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];


    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();     

    self.notesImage.image = newImage;    
 }

So what is happening here is that configureCellWithNote is taking lot of time, because it is resizing images. Please help me out in deciding how can this performance issue be solved.

Regards Rajit

Ranjit
  • 4,576
  • 11
  • 62
  • 121
  • I think ipmcc answered the cache question. BTW, I might be inclined to push this image resizing logic into a `UIImage` category, too. Also, what if the image wasn't 620x200, then this would not work properly. It's safer to use a generic resize algorithm for which you can use `UIViewContentModeScaleAspectFill` or `UIViewContentModeScaleAspectFit`, e.g. http://stackoverflow.com/questions/10491080/uiimage-resizing-not-working-properly/10491692#10491692 – Rob Dec 11 '13 at 16:13

1 Answers1

1

If you simply want to shuffle the resize operation to a background thread, you could do something like this:

- (void)configureCellWithNote:(Folder *)category
{
    self.category = category;

    UIImage *image1 = [UIImage imageWithData:category.noteImage];
    CGSize newSize;

    if(image1.size.width == 620 && image1.size.height == 200)
    {
        newSize = CGSizeMake(300, 97);
    }

    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(), ^{
            self.notesImage.image = newImage;
        });
    });

}

If you want to cache the results, then the trick will be to come up with a good cache key. Unfortunately, it's hard to tell from what you've posted what would make a good cache key. Certainly it will need to include the size, but it'll also need to include something that ties it back to the category. I suppose if nothing else you could use the NSManagedObjectID for the category, but I think that'll be specific to each managed object context you have. Assuming there was a property on Folder called uniqueName a caching implementation might look like this:

- (UIImage*)imageForCategory: (Folder*)category atSize: (CGSize)size
{
    // 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.uniqueName, NSStringFromSize((NSSize)size)];
    // Try fetching any existing image for that key from the cache.
    UIImage* img = [imageCache objectForKey: cacheKey];

    // If we don't find a pre-existing one, create one
    if (!img)
    {
        // Your original code for creating a resized image...
        UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
        UIImage* image1 = [UIImage imageWithData:category.noteImage];
        [image1 drawInRect:CGRectMake(0,0,size.width,size.height)];
        img = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();

        // Now add the newly-created image to the cache 
        [imageCache setObject: img forKey: cacheKey];
    }

    // Return the image 
    return img;
}
ipmcc
  • 29,581
  • 5
  • 84
  • 147
  • Hi, @ipmcc, thanks for your reply, I have a attribute called folder name. So the code which you have shown, should I write it in tableCell.m or in my ViewController.m "CellForRowAtIndexPath" function. And does only this code will handle the caching of images? Sorry to ask this, but I am new to this. Can you please explain the code which you have written. – Ranjit Dec 11 '13 at 15:00
  • It probably makes sense for it to be in the ViewController class and not the TableCell class. This code should handle caching the images. If the cache doesn't contain an image for a specific folder and size, it will generate it. – ipmcc Dec 11 '13 at 15:28
  • +1 On the excellent answer. BTW, given that `NSCache` has changed its behavior effective iOS 7, you might want to subclass `NSCache` to ensure you respond to memory pressure. See http://stackoverflow.com/questions/19546054/nscache-crashing-when-memory-limit-is-reached-only-on-ios-7. Regarding caching in view controller or cell class, if there's any possibility that you might need these resized images elsewhere, you probably don't want it in either of those classes, but rather in some model controller or a singleton that makes it available to other view controllers. – Rob Dec 11 '13 at 16:09
  • @ipmcc, Rob, I want it in cell class, because I am doing all the operations here, so why should I write it in viewController – Ranjit Dec 11 '13 at 18:04
  • I suppose you could make it a class method of your cell class. Practically speaking, it doesn't matter where you put it. Speaking philosophically, you should put it in the most "specific" place where all the things that need to call it can reach it. If it's common to all cells but used by nothing else, then a class method on the tableCell class makes sense. – ipmcc Dec 11 '13 at 18:08
  • @Ranjit That's fine. But as the app grows and you start using thumbnails all over the place, you might not want a separate cache for the thumbnail images for each cell class (or for each view controller, either). This is especially true if you might be showing a particular thumbnail somewhere other than this particular tableview. I personally maintain caches for different functional categories (e.g. thumbnails, full size images, etc.), not for specific individual UI classes. But you can put your cache(s) wherever makes sense for your particular app architecture. – Rob Dec 11 '13 at 18:44
  • Ok thanks both of you. One more thing, Can anybody please explain this piece of code to me briefly – Ranjit Dec 11 '13 at 18:50
  • I've gone through and added comments to the code, being as explicit as I can. If you need more explanation than that, you're going to have to be more specific about what's not clear. – ipmcc Dec 11 '13 at 18:55
  • @Rob, I used the caching process, as explained by ipmcc, but if I dont run the drawRect method in background thread then my app will be still slow. But I dont understand, Once the image is cached app will not enter. "DrawRect" method, but still time profiler shows that it is consuming 60% of the time – Ranjit Dec 12 '13 at 10:53
  • @Ranjit You seem to think that I disagree with the notion of rendering the image in the background. Quite to the contrary, I think you should do that expensive process on background queue. I was only quibbling with the presumption that the cell class should own the cache. – Rob Dec 12 '13 at 11:07
  • @Rob, Nothing like that , I just wanted to clear my doubt.I am little afraid to use multi-thread because, my UI is not loading properly.But anyways, I will figure it out. Thanks for your valuable response. I will go with background thread approach – Ranjit Dec 12 '13 at 11:12
  • @ipmcc , I am working on a ipad app, which has tabs , So here is the case, if I am on a different tab called settings, and I press home button, then I again open the app, it will land me to the settings page, now when I switch tab which contains images, what is happening is that, it is not loading the images from cache,because it is getting nil images and thats why it is again creating it, because of which I am able to see the empty table first and then images loading on to it. Is this because the cache is intialised only once? Should we initialise cache once again. – Ranjit Dec 13 '13 at 11:07
  • Impossible for me to know without seeing it in action, but one main feature of NSCache is that the OS can cause things to be evicted from the cache at any time. If you want a strong guarantee that things you put in the cache will still be there, NSCache is not the right tool. – ipmcc Dec 13 '13 at 12:57
  • @Rob, you suggested me to subclass NSCache, because it is not evicting objects due to memory warning. I noticed one more case, whenever app goes to background, Cache is becoming empty. Is that the case? – Ranjit Dec 16 '13 at 06:19
  • Yup looks like that's a thing: http://stackoverflow.com/questions/13163480/nscache-and-background – ipmcc Dec 16 '13 at 13:50