NSUserDefaults, NSKeyedArchiver & NSCoding (Updated)

(Update) Apple documentation can be confusing - the serialization and archiver programming guide is one such example. There seem to be contradictory statements throughout; the guide doesn't really explain the way archivers, serializers, defaults databases and encoders work with each other. A little bit of further digging on the developer website helped me find a few scattered articles about these terms that were written more recently than the programming guide - I used this information collectively to help elucidate how these APIs and storage methods are related to each other. 

 

NSKeyedArchiver/NSCoder

Objects in memory can’t be passed around to other programs - they contain things like pointers that are only valid in the context of your current virtual memory space (which exists for each process in a multi-tasking OS). Serialization is the process of converting the in-memory representation of an object into a stream of bytes than can be passed between programs. In Cocoa and Cocoa Touch, NSKeyedArchiver is an API that we can use to serialize an object graph. 

If we want an object to be archivable, it must adhere to the NSCoder protocol methods, which encode and decode the objects. encodeWithEncoder and initWithEncoder are the two required protocol methods - the former tells the archiver how to serialize an object into bytes, and the latter tells the archiver how to transform the serialized data in an object. The NSCoding protocol methods go over objects one-by-one and encode both instance value and class type of the current object - the encoded objects are then archived.

You can then write your archive to data (turn the stream into NSData or write directly to a file path (if no path is specified, it goes to the root directory of the application). You can even archive to data and then write to NSUserDefaults (or archive to data and THEN write to a disk). It all just depends on your needs as a developer.

To archive to a data object, use the following code snippet as a guideline:

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.array];

We can then either write to NSUserDefaults or directly to disk - to specify a file path where we want to store our archive, we can do something like this:

NSString *filename  = [NSHomeDirectory() stringByAppendingString:@"/Documents/app.bin"];

[data writeToFile:filename atomically:YES];

If an object graph doesn’t need to be serialized, and instead, an object hierarchy needs to be stored, we can skip archiving and go directly to NSUserDefaults (assuming we aren’t talking about custom objects). 

 

NSUserDefaults

NSUserDefaults provides a way to store small amounts of information, largely user preferences, in a “defaults database”. These parameters are called defaults because they’re commonly used to determine an application's default state at startup. At runtime, unboxing this defaults database can be accomplished with NSUserDefaults; in order to avoid constantly reading from that database throughout the lifecycle of an application, values can be cached. A method called synchronize is automatically called every so often, keeps the cached data synced with the defaults database.

NSUserDefaults can be used to store NSData, NSStrings, NSNumbers, NSDates, NSArrays or NSDictionaries. The call to NSUserDefaults is a thread-safe singleton that we use to set up a defaults database. We can also include a check when the app is running to see whether we have a database currently being used - if we do, use it, if not, create an instance using the singleton. 

In saving to our sharedInstance (the singleton) of NSUserDefaults, we can use the setObject: forKey: method, which takes any of the NSUserDefaults compatible objects and stores it with a key for quick lookup. We then synchronize to make sure that our cache is up to date. When retrieving, we call our sharedInstance and grab objectForKey:@“” and pass the key we know is associated with the object. 

*NOTE: If we want to store custom objects in NSUserDefaults (we may have such a small amount of data to persist that this makes sense), then we must implement NSCoder protocol methods and archive the objects. 

 

Conclusion

Trying to store custom objects? 

Implement NSCoding protocol methods to create a byte stream and use NSKeyedArchiver to create an archive of the data. If custom objects aren’t part of the equation, you can just go straight to NSUserDefaults.

 

So you’ve created an archive of encoded custom objects…where and how can you store them?

You can write your archive to an NSData object, and then either write that to disk or store it in NSUserDefaults. Alternatively, you can just write your archive directly to disk, either by specifying a file path or using the default one (the root directory of the application).