2013-12-11 21 views
6

NSArchiver因爲OS X 10.2已被棄用,不可用AFAIK iOS上子類NSCoder,再造NSArchiver

在另一方面,NSKeyedArchiver已知缺乏對速度&簡明部分(some users報告100多次性能差異在NSKeyedArchiverNSArchiver之間)。我要歸檔的對象主要是NSObject的子類,其中包含NSMutableArrayNSNumber,以及包含原始類型的對象(主要爲double)。我不相信,開銷密鑰存檔意味着值得。

所以我決定在iOS上子類NSCoder創建一個串行編碼器,風格爲NSArchiver

我知道鍵控存檔可能派上用場:向前兼容性和其他細節,它可能是我最終會用到的,但我很想知道我可以通過串行存檔獲得什麼類型的演出。坦率地說,我認爲我可以通過這樣做來學習很多東西。所以我不是感興趣的替代解決方案;

我一直的Cocotron來源的啓發,提供了一個開源的NSArchiver

TLDR:我想繼承NSCoder重建NSArchiver


我使用ARC,爲編制iOS 6 & 7,並假設現在是32位系統。我只是使用NSHashTableweakObjectsHashTable)來防止類名重複:類將在第一次遇到時被描述,然後通過引用引用它們。

我用一個NSMutableData建立檔案:

@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:轉儲原始類型,例如intcharfloatdouble ...等。沒有什麼有趣的。

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

解碼方式幾乎相同; 解析程序擁有它已知的類的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; 

} 

最後,中心的方法(我w烏爾德感到驚訝,如果這個問題是在這裏某處)

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


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

對應的encodeWithCoder:以前的片段中initWithCoder:方法會去這樣的事情

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

decodeObject實現是完全_extractObject

現在,這一切都應該很好,很好。事實上,我能夠存檔/取消存檔我的一些對象。檔案看起來很好,只要我願意在十六進制編輯器中檢查它們,我就可以解除某些包含double的其他類的NSMutableArray的自定義類的存檔。


但由於某些原因,如果我嘗試解檔包含的NSMutableArrayNSNumber我的目標之一,我碰到這個問題:

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

似乎每NSNumber一行在數組,並且地址0xc112cc對於每一行都是相同的。在malloc_error_break中放置斷點告訴我錯誤來自-[NSPlaceholderNumber initWithCoder:](從我的_extractObject方法中調用)。

這是一個與我使用ARC有關的問題嗎?我錯過了什麼?

+0

你能分享一個最小的initWithCoder:實現嗎? – James

+0

我用示例'initWithCoder:'實現更新了我的帖子。這非常簡單。 – Olotiar

+0

它可能與標記指針有關,檢查是否是您正在歸檔的實際值「0xc112cc」? –

回答

2

我的錯誤與在type代表C字符串(*type == '*')的情況下-(void)encodeValueOfObjCType:(const char *)type at:(const void *)addr的第二個參數的誤解有關。在那種情況下,addrconst char **,指向const char *的指針,它本身指向應該被編碼的常數0終止數組char

NSNumber encodeWithCoder:編碼表示可變背襯的類型的值小的C-字符串(iintd雙等它等於AFAIK到@encode指令)。

我以前的曲解(假設addrconst char *)製成的不正確的編碼/解碼,並且因此initWithCoder:是失敗(底線:它試圖free堆棧變量,從而該錯誤消息和一個事實,即地址對於函數的每次調用總是相同的)。

我現在有一個工作實現。 如果有人有興趣,代碼在我的GitHub MIT許可下。