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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
NSCoding Tutorial for iOS: How To Save Your App Data
25 mins
Scary Bug Database
We’ve already seen one thing we need the Scary Bug Database object to do – figure out an available directory name for a new bug document.
But there’s one other thing we need it to do too – figure out what bug document directories are there on startup, and loop through creating ScaryBugDocs and calling initWithDocPath for each.
So let’s put these together! Right click on Model, go to “Add\New File…”, and add a new Cocoa Touch Class\Objective-C class\Subclass of NSObject. Name the new file ScaryBugDatabase.m, and click Finish.
Then make the following mod to ScaryBugDatabase.h:
// Add to bottom of file
+ (NSMutableArray *)loadScaryBugDocs;
+ (NSString *)nextScaryBugDocPath;
So we’re just creating two static methods – one to load the scary bug documents (and return them in a NSMutableArray), and the one we were using earlier to get the next available path.
Now let’s implement these, bit by bit.
1) Write helper function to get document root
// Add to top of file
#import "ScaryBugDoc.h"
// After @implementation, add new function
+ (NSString *)getPrivateDocsDir {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
documentsDirectory = [documentsDirectory stringByAppendingPathComponent:@"Private Documents"];
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtPath:documentsDirectory withIntermediateDirectories:YES attributes:nil error:&error];
return documentsDirectory;
}
The other two functions are going to need a function to tell them where to save the bug documents subdirectories.
One of the most common places to save data for your apps is in the “Documents” directory for your app. You can get the path to this with NSSearchPathForDirectoriesInDomains, passing in NSDocumentDirectory as the value.
However, we don’t want to save here because in the next tutorial in our series, we’re going to extend this app even further to support the new File Sharing feature on iOS 4 and the iPad. The way file sharing works is it shows anything you save in the documents directory to the user, but for reasons we’ll get into later we don’t want the user seeing these directories as-is. According to an Apple doc on Storing Private Data, the recommended solution is to save your app’s data in a subdirectory of Library, so that’s what we’re doing here.
So – we construct the path (which will be
2) Write helper function to load all existing documents
+ (NSMutableArray *)loadScaryBugDocs {
// Get private docs dir
NSString *documentsDirectory = [ScaryBugDatabase getPrivateDocsDir];
NSLog(@"Loading bugs from %@", documentsDirectory);
// Get contents of documents directory
NSError *error;
NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:&error];
if (files == nil) {
NSLog(@"Error reading contents of documents directory: %@", [error localizedDescription]);
return nil;
}
// Create ScaryBugDoc for each file
NSMutableArray *retval = [NSMutableArray arrayWithCapacity:files.count];
for (NSString *file in files) {
if ([file.pathExtension compare:@"scarybug" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
NSString *fullPath = [documentsDirectory stringByAppendingPathComponent:file];
ScaryBugDoc *doc = [[[ScaryBugDoc alloc] initWithDocPath:fullPath] autorelease];
[retval addObject:doc];
}
}
return retval;
}
Here we get our documents directory, and use contentsOfDirectoryAtPath to get a list of files/directories inside. We loop through them all, looking for files/directories ending with the “scarybug” extension, which we’re using to represent a directory with our scary bug data. When we find them, we construct the full path, then create a ScaryBugDoc with the initWithDocPath initializer and add it to our list.
3) Write helper function to get next available document path
+ (NSString *)nextScaryBugDocPath {
// Get private docs dir
NSString *documentsDirectory = [ScaryBugDatabase getPrivateDocsDir];
// Get contents of documents directory
NSError *error;
NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:&error];
if (files == nil) {
NSLog(@"Error reading contents of documents directory: %@", [error localizedDescription]);
return nil;
}
// Search for an available name
int maxNumber = 0;
for (NSString *file in files) {
if ([file.pathExtension compare:@"scarybug" options:NSCaseInsensitiveSearch] == NSOrderedSame) {
NSString *fileName = [file stringByDeletingPathExtension];
maxNumber = MAX(maxNumber, fileName.intValue);
}
}
// Get available name
NSString *availableName = [NSString stringWithFormat:@"%d.scarybug", maxNumber+1];
return [documentsDirectory stringByAppendingPathComponent:availableName];
}
Here we basically loop through our documents directory looking for “scarybug” directories, which should be in the format “#.scarybug”. We loop through to find the max number we currently have, and then return just one more than that number. That’s just one easy way to get a unique name for us to use.
Ok we’re almost done! All we need to do now is integrate this with the rest of the app and try it out!
Try it Out!
This is pretty easy. Open up ScaryBugsAppDelegate.m and make the following changes:
// Add to top of file
#import "ScaryBugDatabase.h"
// Comment out the code to load the sample ScaryBugDoc data in the beginning of application:didFinishLaunchingWithOptions, and replace it with the following:
NSMutableArray *loadedBugs = [ScaryBugDatabase loadScaryBugDocs];
RootViewController *rootController = (RootViewController *) [navigationController.viewControllers objectAtIndex:0];
rootController.bugs = loadedBugs;
That handles loading of bugs. But we need to save bugs too! Make the following changes to EditBugViewController.m:
// In titleFieldValueChanged
[_bugDoc saveData];
// In rateView:ratingDidChange
[_bugDoc saveData];
We go ahead and just save the doc immediately every time the title or rating changes. Since our data is so ridiculously small, this is fine for performance, but if you have a larger data set you might want to save periodically in the background instead, or when the user terminates the app or enters the background, etc.
Lastly, make the following mod to RootViewController.m:
// In tableView:commitEditingStyle:forRowAtIndexPath, before removing the object from the bugs array:
ScaryBugDoc *doc = [_bugs objectAtIndex:indexPath.row];
[doc deleteDoc];
That’s it! Compile and run your app. When it starts up, you’ll see your table view with no bugs in it. However, that’s fine because no bugs are on disk yet. You can double check this by going to Run\Console, and scrolling to the bottom: it will print out where it’s looking for bug documents:
Loading bugs from /Users/rwenderlich/Library/Application Support/ iPhone Simulator/4.0.2/Applications/ D13C7304-25FB-4EDC-B23D-62A084AD90B4/Library/Private Documents
If you open up that directory in Finder, you’ll see it empty of course. So go ahead and create a bug in the app, and when you do you’ll see a new directory pop up in your Private Docs directory:
Also, you may find it interesting to double click on the data.plist file to see what that looks like as well:
So as you can see since we’re using NSCoding + NSKeyedArchiver, our data is being saved out in a property list that is semi-human readable, which can be kind of nice for debugging sometimes.
Now shut down Scary Bugs, and start it back up again. (Make sure you fully shut it down and restart, not just enter into the background!) When you do, you’ll see that it correctly loads the bug you created back in from disk, but you’ll notice that it didn’t save the image.
That of course is because we didn’t save it! So let’s take care of that next.