Saving structs with NSValue

3 Aug

Janie details her efforts persisting data to disk.

Something crept into my mind while reading her post; this seems like a case that NSValue could address well.

NSCoding and NSDictionary are good ways to persist values to disk. But if you have a small struct that you don’t expect to change the definition of, NSValue might be the right tool for the job. NSValue conforms to NSCoding, or, more precisely, NSSecureCoding. That means you can use NSKeyedArchiver to save any NSValue to disk however you like.

NSValue has some nice connivence methods that allow you to easily deal with rects, sizes, and points. To get a value representing a rect that you can save to disk is just one line:

NSValue *aValue = [NSValue valueWithRect:someRect];

This is handy, but NSValue‘s real power starts to show when we look at [NSValue valueWithBytes:objCType:]. Don’t let the objCType name fool you, this method works with C types too. With this method we can convert C types, like structs, to NSValue objects for easy archiving.

Armed with this knowledge, lets see how we might use NSValue to address Janie’s coordinate issue.

typedef struct {
    double x;
    double y;
    double z;
} Coordinate;

@implementation NSValue (Coordinate)
+ (NSValue *)valueWithCoordinate:(Coordinate)coord {
    return [NSValue valueWithBytes:&coord objCType:@encode(Coordinate)];

- (Coordinate)coordinateValue {
    Coordinate coord;
    [self getValue:&coord];
    return coord;

That small category allows us to work with Coordinates as NSValues real easily.

Coordinate coord;
coord.x = 1.1;
coord.y = 2.2;
coord.z = 3.3;
NSValue *coordValue = [NSValue valueWithCoordinate:coord];

And to get the Coordinate back out of the value is just one line:

Coordinate coord2 = [coordValue coordinateValue];

That’s all there is to it.

There is one gotcha though. As I said earlier, this is really only a good solution if you’re relatively certain that your struct’s definition won’t change over the life of your app. This is because NSValue doesn’t have any knowledge of the data you save into it. It treats the data as just a blob of bytes. So, if we decided that we really only need x and y so we delete z, we would break our app.

I would argue that a coordinate struct is very unlikely to change, and that if it did change significatly we might be better suited by creating a whole new struct anyway. Say, Coordinate2D vs the original Coordinate struct.

So, if you’re thinking about archiving a struct, consider NSValue, it might be just the tool you’re looking for.