6

NSArchiver自 OS X 10.2 起已弃用,并且在 iOS 上不可用 AFAIK

另一方面,NSKeyedArchiver众所周知,它缺乏速度和简洁性部分(一些用户NSKeyedArchiver报告和之间的性能差异超过 100 倍NSArchiver)。我要归档的对象主要是NSObject包含NSMutableArrayof 的子类NSNumber,以及包含原始类型的对象(主要是double)。我不相信开销键控归档意味着值得。

所以我决定NSCoder在 iOS 上进行子类化,以创建一个串行编码器,其风格为NSArchiver.

我知道键控存档可能会派上用场:向后向前兼容性和其他细节,这可能是我最终将使用的,但我很想知道我可以通过串行存档获得什么样的性能。坦率地说,我认为我可以通过这样做学到很多东西。所以我对替代解决方案不感兴趣;

我受到了Cocotron的来源的启发,提供了一个开源NSArchiver

TLDR:我想子类NSCoder重建NSArchiver


我正在使用 ARC,为 iOS 6 和 7 编译,并假设现在是 32 位系统。

我现在对引用对象或字符串不感兴趣,我只是使用NSHashTable( weakObjectsHashTable) 来防止类名重复:类将在第一次遇到时进行描述,然后通过引用进行引用。

我正在使用 aNSMutableData来构建存档:

@interface Archiver {
    NSMutableData *_data;
    void *_bytes;
    size_t _position;
    NSHashTable *_classes;
}
@end

基本方法是:

-(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;
}

_appendBytes:length:用来转储原始类型,例如 int, char, float, double... 等。那里没有什么有趣的。

使用这种同样无趣的方法转储 C 风格的字符串:

-(void)_appendCString:(const char*)cString
{
    NSUInteger length = strlen(cString);
    [self _appendBytes:cString length:length+1];

}

最后,归档类信息和对象:

-(void)_appendReference:(id)reference {
    [self _appendBytes:&reference length:4];
}

-(void)_appendClass:(Class)class
{
    // NSObject class is always represented by nil by convention 
    if (class == [NSObject class]) {
        [self _appendReference:nil];
        return;
    }

    // Append reference to class
    [self _appendReference:class];

    // And append class name if this is the first time it is encountered
    if (![_classes containsObject:class])
    {
        [_classes addObject:class];
        [self _appendCString:[NSStringFromClass(class) cStringUsingEncoding:NSASCIIStringEncoding]];
    }
}

-(void)_appendObject:(const id)object
{
    // References are saved
    // Although we don't handle relationships between objects *yet* (we could do it the exact same way we do for classes)
    // at least it is useful to determine whether object was nil or not
    [self _appendReference:object];

    if (object==nil)
        return;

    [self _appendClass:[object classForCoder]];
    [object encodeWithCoder:self];

}

我的对象的encodeWithCoder:方法都是这样的,没什么特别的:

[aCoder encodeValueOfObjCType:@encode(double) at:&_someDoubleMember];
[aCoder encodeObject:_someCustomClassInstanceMember];
[aCoder encodeObject:_someMutableArrayMember];

解码方式几乎相同;unarchiver 拥有一个NSMapTable它已经知道的类,并查找它不知道的类引用的名称。

@interface Unarchiver (){
    NSData *_data;
    const void *_bytes;
    NSMapTable *_classes;
}

@end

我不会用细节来烦你

-(void)_extractBytesTo:(void*)data length:(NSUInteger)length

-(char*)_extractCString

有趣的东西可能在对象解码代码中:

-(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;

}

最后,中心方法(如果问题出在此处,我不会感到惊讶)

-(void)decodeValueOfObjCType:(const char *)type at:(void *)data
{


    switch(*type){
        /* snip, snip */
        case '@':
            *(id __strong *) data = [self _extractObject];
            break;
    }
}

initWithCoder:对应于前一个片段的方法会encodeWithCoder:像这样

if (self = [super init]) {
    // Order is important
    [aDecoder decodeValueOfObjCType:@encode(double) at:& _someDoubleMember];
    _someCustomClassInstanceMember = [aDecoder decodeObject];
    _someMutableArrayMember = [aDecoder decodeObject]; 
}
return self;

我的decodeObject实现正是_extractObject.

现在,所有这些都应该运行良好。事实上;我能够归档/取消归档我的一些对象。档案看起来不错,在某种程度上我愿意在十六进制编辑器中检查它们,并且我能够取消归档我的一些自定义类,其中包含NSMutableArrays 的一些其他包含doubles 的类。


但是由于某种原因,如果我尝试取消归档包含 a NSMutableArrayof的对象之一NSNumber,我会遇到这个问题:

malloc: *** error for object 0xc112cc: pointer being freed was not allocated

NSNumber数组中似乎有一行,并且0xc112cc每一行的地址都是相同的。放一个断点malloc_error_break告诉我错误来自-[NSPlaceholderNumber initWithCoder:](从我的_extractObject方法调用)。

这与我对 ARC 的使用有关吗?我错过了什么?

4

1 回答 1

2

我的错误与在表示 C 字符串 ( )-(void)encodeValueOfObjCType:(const char *)type at:(const void *)addr的情况下对第二个参数的误解有关。在这种情况下是 a ,指向 the 的指针,它本身指向应该编码的常量、终止数组。type*type == '*'addrconst char **const char *0char

NSNumber encodeWithCoder:编码一个小的 C 字符串,表示支持该值的变量的类型(一个ifor int, for double 等。它与指令d相等)。@encode

我之前的误解(假设addr是 a const char *)进行了不正确的编码/解码,initWithCoder:因此失败了(底线:它正在尝试free堆栈变量,因此出现错误消息以及地址对于每次调用总是相同的事实功能)。

我现在有一个有效的实现。如果有人感兴趣,代码在我的GitHub 上,使用 MIT 许可。

于 2014-01-06T09:29:53.903 回答