Nat! bio photo

Nat!

Senior Mull

Twitter Github Twitch

NSCoder: Surprisingly fragile - NSKeyedArchiver: surprisingly useless

In MulleScion, I am using NSArchiver to create a cached, compressed and persistant copy of a scion document. On paper this seemed like a good idea. But I never tested it very much.

On iOS there is no NSArchiver, but only NSKeyedArchiver. Technically though, you can substitute the NSArchiver class by NSKeyedArchiver, since both are NSCoder subclasses.

Lets try a simple example, where a class Foo chains Foo instances together. Each instance carries a NSUInteger value load:


@interface Foo : NSObject
{
   NSUInteger  _longValue;
   Foo         *_next;
}

@property( assign, nonatomic) NSUInteger   longValue;
@property( retain, nonatomic) Foo          *next;

@end

Without keys, this could be encoded like this:


- (void) encodeWithCoder:(NSCoder *) encoder
{
   [encoder encodeValuesOfObjCTypes:"l@", &_longValue, &_next];
}

or with keys like this:


- (void) encodeWithCoder:(NSCoder *) encoder
{

   [encoder encodeInt32:_longValue
                 forKey:@"longValue"];

   [encoder encodeObject:_next
                  forKey:@"next"];
}

I then chained 10000 Foo objects together, archived them and wrote them into a file. Here's the git project NSKeyedEncodingTest for you to try it out yourself.

NSKeyedArchiver files are huge

The size differences between NSArchiver and NSKeyedArchiver are quite dramatic.

without keys with keys
NSArchiver 68658 bytes
NSKeyedArchiver 342653 bytes 342662 bytes

The NSKeyedArchiver file is 5 times larger.

In my actual MulleScion case, the NSArchiver file is about 50% smaller ( I also compress the archived data) than my source template, where as the NSKeyedArchiver file is about three times as large.

NSKeyedArchiver is slow

I made some test cases where the aforementioned 10000 Foo objects are just read from an archive and then written back. This is done 100 times. These are the results:

without keys with keys
NSArchiver 1.5s
NSKeyedArchiver 4.2s 4.2s

NSKeyedArchiver file is 3 times slower.

All NSCoders eventually crash

If I chain 40000 Foo objects together, all NSCoders crash on archiving, keyed or unkeyed, because of stack exhaustion. Surprising ? Well not that surprising, since NSCoding is recursive by nature. But it's something to be aware of. When I recode the method thusly:


- (void) encodeWithCoder:(NSCoder *) encoder
{
   [encoder encodeValuesOfObjCTypes:"l", &_longValue];
   [encoder encodeObject:_next];
}

NSArchiver makes it through the 40000 objects barrier, but it will also crash eventually around 60000 objects. Is 60000 enough objects in all cases ? Maybe so, maybe not so.

Summary

There are likely very few applications, where it pays off to use NSKeyedArchiver to cache an object graph. It's neither a compact format, nor a fast coding method. You might be better off just reparsing the source. I parse my templates just about as fast as NSArchiver can unarchive. I can see where the added compression and the lack of need for extra I/O to read included files may give NSArchiver an advantage. NSKeyedArchiver though, just makes everything worse for me.