NSCoding Tutorial for iOS: How To Save Your App Data

An NSCoding tutorial on how to quickly and easily save your app’s data with NSCoding and NSFileManager in iOS. By Ray Wenderlich.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 3 of this article. Click here to view the first page.

Saving and Loading Images

Ok so we have this big image and thumbnail image that we need to save to disk. How should we do that?

You may think that we should just encode it with NSCoding just like we did with the ScaryBugDoc data above. NSCoding does support this behavior (just convert it to NSData with UIImagePNGRepresentation and then use encodeObject:forKey, and then call decodeObjectForKey / UIImage initWithData on the other side), but it’s usually not the best way to go.

This is because you usually don’t want to save huge pieces of data inside your main document if you can avoid it.

Note how we load up all of the documents into a table view at startup, which access their data property and hence decodes the ScaryBugData from disk. If we included the images in this data, on startup it would load all of the big images into memory at once too. This would mean it would take longer for our app to start up, and we’d have a higher memory cost because we’d have to keep all of the images in memory at a time.

But we don’t need to do that, because we don’t even look at the full images until we go into the detail view! So it would be a lot better if we just saved the images to disk, and only loaded them into memory as they were accessed, and relased them from memory when we weren’t using them anymore.

So let’s do that and see what it looks like. Much like the way we saved the data of our ScaryBugDoc, we’re going to override the properties that get our images, and add a “saveImage” method as well.

Make the following mod to ScaryBugDoc.h:

// After @interface
- (void)saveImages;

Then add the following to ScaryBugDoc.m:

// Add to top of file
#define kThumbImageFile @"thumbImage.jpg"
#define kFullImageFile  @"fullImage.jpg"

// Add new functions
- (UIImage *)thumbImage {
 
    if (_thumbImage != nil) return _thumbImage;
    
    NSString *thumbImagePath = [_docPath stringByAppendingPathComponent:kThumbImageFile];
    return [UIImage imageWithContentsOfFile:thumbImagePath];
        
}

- (UIImage *)fullImage {
    
    if (_fullImage != nil) return _fullImage;

    NSString *fullImagePath = [_docPath stringByAppendingPathComponent:kFullImageFile];
    return [UIImage imageWithContentsOfFile:fullImagePath];
    
}

Here we check to see if we have the images loaded into memory already, and if we do we return them. Otherwise, we just load the images straight off the disk with imageWithContentsOfFile.

Note that we don’t cache the images in our instance variables. This is to avoid the situation where someone loads up the detail view for each of our images, and the images are still retained inside this class because they were loaded once, clogging up memory with images. However, the tradeoff is that images might have to be loaded from disk more often than necessary. If this gets to be problematic, one solution is to retain them in instance variables, but have a method to clear the cache that is called under low memory conditions.

Then add the following as well:

- (void)saveImages {
    
    if (_thumbImage == nil || _fullImage == nil) return;
    
    [self createDataPath];
    
    NSString *thumbImagePath = [_docPath stringByAppendingPathComponent:kThumbImageFile];
    NSData *thumbImageData = UIImagePNGRepresentation(_thumbImage);
    [thumbImageData writeToFile:thumbImagePath atomically:YES];
    
    NSString *fullImagePath = [_docPath stringByAppendingPathComponent:kFullImageFile];
    NSData *fullImageData = UIImagePNGRepresentation(_fullImage);
    [fullImageData writeToFile:fullImagePath atomically:YES];
    
    self.thumbImage = nil;
    self.fullImage = nil;
    
}

Here we just write both images out to disk and set our cached copies back to nil (for the same reasons discussed above).

Now we just need to call the code to save the images at the right spot. Modify EditBugViewController.m to make the following change:

// In imagePickerController:didFinishPickingMediaWithInfo, after _imageView.image = fullImage:
[_bugDoc saveImages];

That’s it! Complie and run your app, and change the picture for a bug. Afterwards you’ll see your document directory contains the images:

Scary Bug Document with Images

And now if you shut down and restart your app, you’ll see that the images have been persisted as well!

Our Bug Saved To Disk!

Where To Go From Here?

Here is a sample project with all of the code we’ve developed in the above NSCoding tutorial.

In the next tutorial, we cover how to enable this app to support the new File Sharing feature in iTunes and add the ability to export/import bugs via email!