NSArchiver deprecated with OS X 10.2 and is not available AFAIK on iOS
On the other hand, it is known that NSKeyedArchiver lacks part of speed and conciseness ( some users report performance more than 100 times the difference between NSKeyedArchiver and NSArchiver ). The objects that I want to archive are mainly subclasses of NSObject containing NSMutableArray of NSNumber , and objects containing primitive types (mostly double ). I am not convinced that key invoices mean value.
So, I decided to subclass NSCoder on iOS to create a serial encoder in the style of NSArchiver .
I understand where archives with keys can be useful: backward compatibility and other subtleties, and probably this is what I end up using, but I would be interested to know what performances I can get with serial archiving. And frankly, I think I could learn a lot by doing this. Therefore, I am not interested in an alternative solution;
I was inspired by Cocotron sources, providing the open source NSArchiver
TL; DR : I want to subclass NSCoder rebuild NSArchiver
I am using ARC, compiling for iOS 6 and 7 and now assuming a 32-bit system.
At the moment, I'm not interested in references to objects or strings. I use only NSHashTable ( weakObjectsHashTable ) to prevent duplication of class names: classes will be described when they are first discovered, and then passed by reference.
I use NSMutableData to build the archive:
@interface Archiver { NSMutableData *_data; void *_bytes; size_t _position; NSHashTable *_classes; } @end
The main methods:
-(void)_expandBuffer:(NSUInteger)length { [_data increaseLengthBy:length]; _bytes = _data.mutableBytes; } -(void)_appendBytes:(const void *)data length:(NSUInteger)length { [self _expandBuffer:length]; memcpy(_bytes+_position, data, length); _position += length; }
I am using _appendBytes:length: to discard primitive types like int , char , float , double ... etc. There is nothing interesting.
C-style strings are discarded using this equally interesting method:
-(void)_appendCString:(const char*)cString { NSUInteger length = strlen(cString); [self _appendBytes:cString length:length+1]; }
And finally, archiving information and class objects:
-(void)_appendReference:(id)reference { [self _appendBytes:&reference length:4]; } -(void)_appendClass:(Class)class {
The methods of encodeWithCoder: my objects look like this, nothing fancy:
[aCoder encodeValueOfObjCType:@encode(double) at:&_someDoubleMember]; [aCoder encodeObject:_someCustomClassInstanceMember]; [aCoder encodeObject:_someMutableArrayMember];
Decoding happens in much the same way; The unarchiver has NSMapTable classes that it already knows, and is looking for the name of a reference to a class that it does not know about.
@interface Unarchiver (){ NSData *_data; const void *_bytes; NSMapTable *_classes; } @end
I will not bore you with the specification
-(void)_extractBytesTo:(void*)data length:(NSUInteger)length
and
-(char*)_extractCString
Interesting material is probably in the object decoding code:
-(id)_extractReference { id reference; [self _extractBytesTo:&reference length:4]; return reference; } -(Class)_extractClass { // Lookup class reference id classReference = [self _extractReference]; // NSObject is always nil if (classReference==nil) return [NSObject class]; // Do we already know that one ? if (![_classes objectForKey:classReference]) { // If not, then the name should follow char *classCName = [self _extractCString]; NSString *className = [NSString stringWithCString:classCName encoding:NSASCIIStringEncoding]; free(classCName); Class class = NSClassFromString(className); [_classes setObject:class forKey:classReference]; } return [_classes objectForKey:classReference]; } -(id)_extractObject { id objectReference = [self _extractReference]; if (!objectReference) { return nil; } Class objectClass = [self _extractClass]; id object = [[objectClass alloc] initWithCoder:self]; return object; }
And finally, the central method (I wonβt be surprised if the problem is somewhere here)
-(void)decodeValueOfObjCType:(const char *)type at:(void *)data { switch(*type){ case '@': *(id __strong *) data = [self _extractObject]; break; } }
The initWithCoder: corresponding to the previous encodeWithCoder: will look something like this.
if (self = [super init]) { // Order is important [aDecoder decodeValueOfObjCType:@encode(double) at:& _someDoubleMember]; _someCustomClassInstanceMember = [aDecoder decodeObject]; _someMutableArrayMember = [aDecoder decodeObject]; } return self;
My implementation of decodeObject is definitely _extractObject .
Now all this should work well and well. And really; I can archive / unlock some of my objects. The archives look great as far as I am ready to test them in a hex editor, and I can unlock some of my custom classes containing NSMutableArray another class containing double s.
But for some reason, if I try to parse one of my objects containing NSMutableArray of NSNumber , I ran into this problem:
malloc: *** error for object 0xc112cc: pointer being freed was not allocated
There seems to be one row in the NSNumber in NSNumber , and the address 0xc112cc for each row. Putting a breakpoint in malloc_error_break tells me that the errors are from -[NSPlaceholderNumber initWithCoder:] (called from my _extractObject method).
Is this a problem with my use of ARC? What am I missing?