NSCoding, Without the Boilerplate

In Object Serialization With NSCoding we talked about how your can use the NSKeyedArchiver and the NSCoding protocol to easily save your custom model objects as Binary Plists for later retrieval. As a reminder, here is how we would implement NSCoding for a simple class “Foo”, with three properties:

NSCoding eliminates a lot of the complexity of saving objects by automatically handling circular references, duplicate objects, etc. But you still have to write those pesky initWithCoder: and encodeWithCoder: methods for every class. That’s kinds of a drag. Implementing NSCoding can involve a lot of boilerplate, especially if your object has a lot of properties to encode. Is there anything we can do about that?

Work Smarter, Not Harder

Let’s look at what we actually need to do for each property that we’re encoding. For each property we’ve written the following:

That’s four references to the name of our property. Two of the references are selectors, two of them are strings. Not very DRY. We need to find some way to eliminate this repetition.

We can make use of KVC (Key-Value Coding) to set and get properties by name. KVC also has the neat feature that it can automatically box and unbox primitive values such as integers or booleans into their equivalent object type (e.g. NSNumber), so we don’t have to worry about using different methods to encode our properties, we can treat them all as objects. Using KVC we can rewrite our encode/decode calls as follows:

Because we’ve eliminated any references to the specific property name in our code, we can now make it reusable. By looping through an array of property names, we can encode/decode all our properties easily. That means we can create a common base class for all our codable objects that does most of the work for us:

To implement our NSCodable Foo class from earlier, all we have to do now is this:

So much nicer! But if we can reduce our encoding down to an array of property names, can’t we just get Cocoa to figure that bit out for us too?

Introspection FTW!

The objective-C runtime can help us out here. Using a bit of runtime magic, we can find out the names of all the properties of our class and generate the propertyNames array automatically in our CodableObject class. Here’s the code to do that:

Now any object that inherits from CodableObject supports NSCoding automatically without needing to override the propertyNames method. Sweet! There are a few caveats though:

1) This mechanism won’t encode properties inherited from a superclass. CodableObject doesn’t have any properties, but if we had a deeper inheritance structure (e.g. Dog > Animal > CodableObject) then the properties inherited from the Animal class wouldn’t get coded when we save an object of class Dog.

2) Not every property can be NSCoded. If a property is both virtual (not backed by an ivar) and readonly, it can’t be set using setValueForKey:, which means it will crash our initWithCoder: method. If it’s readonly and has an ivar, but the ivar name doesn’t match the property name, setValueForKey: won’t work either. Encoding readwrite virtual properties will work, but it’s probably pointless – if they have no backing ivar, they are probably computed values and don’t need to be saved. To be safe, we really only want to encode properties that have a backing ivar with the same name.

3) Not every property will be codable. If it’s something like a struct or pointer, or an object which doesn’t itself support NSCoding, it won’t work.

4) Sometimes we may want to omit properties from coding. For example if our class contains something like a timer, or some flags used purely for tracking runtime state, we may not wish to save those.

To fix issue 1 we need to loop through the object’s superclasses and make sure we capture all the properties in those classes too. To fix issue 2 we need to check some extra metadata about each property. Our improved propertyNames method looks like this:

That solves issue 1 and 2 and should now work for all common cases. There isn’t any simple solution to 3, but it’s not a big deal because we can avoid adding properties to our classes that can’t be auto-coded in most cases, and when we can’t, we can just override the NSCoding methods and handle them manually as a special case.

What about issue 4? How can we omit properties from being coded? If the property is private, we can just declare an ivar and no property declaration. We only loop through properties, so ivars with no associated property won’t be encoded.

For public properties, we can always override our propertyNames array to omit properties we don’t want to save, but that’s a bit messy. Fortunately our solution to problem 2 gives us a simple way to exclude certain properties from coding. We exclude properties from the propertyNames array if their ivar doesn’t match the name of the property. We do this out of technical necessity, but we can take advantage of it here. If we add a synthesise statement that defines the property as having a non-compliant name, it won’t be encoded. E.g.

One last thing: The propertyNames method is pretty fast, but it seems inefficient to call it every time we encode or decode a class since the property names are decided at compile time and aren’t likely to ever change during program execution. Is there some way we can cache the propertyNames array for each class (preferably without needing to add extra properties or methods to our subclasses)?

Cool By Association

In Adding Properties to a Category Using Associated Objects we talked about using associated objects to add extra data to existing objects. Are you thinking what I’m thinking? No? OK, well I’m thinking we should use the associated objects mechanism to store our propertyNames array in the class once it has been calculated for the first time. You can do that as follows:

And there we have it. Automatic NSCoding for all your classes, without the boilerplate.

NSCoding Additional Reading

Nick Lockwood is the author of iOS Core Animation: Advanced Techniques. Nick also wrote iCarousel, iRate and other Mac and iOS open source projects.
  1. Nice work! I’ve been toying around with the Mantle framework, which is your post taken REALLY far. It’s nice to just see the basics here as sometimes Mantle becomes and unwieldy beast…

Comments are closed.