2013-12-11 3 views
6

NSArchiver устарела, так как OS X 10.2, и не доступен AFAIK на прошивкойподклассов NSCoder, воссоздавая NSArchiver

С другой стороны, NSKeyedArchiver, как известно, не хватает на краткость части скорости & (some users отчет более чем 100 раз разница рабочих характеристик между NSKeyedArchiver и NSArchiver). Объектами, которые я хочу архивировать, являются в основном NSObject подклассы, содержащие NSMutableArray из NSNumber, и объекты, содержащие примитивные типы (в основном double). Я не убежден в том, что накладные документы с ключами подразумевают ценность.

Итак, я решил подкласс NSCoder на iOS создать серийный кодер в стиле NSArchiver.

Я понимаю, где могут быть полезны архивы с ключами: совместимость в обратном направлении и другие тонкости, и, вероятно, это то, что я в конечном итоге использую, но мне было бы интересно узнать, какие выступления я могу получить с последовательным архивированием , И, честно говоря, я думаю, что я мог бы многому научиться, делая это. Так что я не заинтересован в альтернативном решении;

Я был вдохновлен источников Cocotron, обеспечивая открытый источник NSArchiver

TLDR: Я хочу подкласс NSCoder восстановить NSArchiver


Я использую ARC, для компиляции iOS 6 & 7, и предполагая 32-битную систему на данный момент.

Я не заинтересован в ссылках объектов или строки на данный момент, я только с помощью NSHashTable (weakObjectsHashTable), чтобы предотвратить имена классов должны быть продублировано: классы будут описаны первый раз, когда они встречаются, а затем называют по ссылке ,

Я использую 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: сваливать примитивных типов, таких как 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; 

} 

И, наконец, центральный метод (I w не ульд удивляйтесь, если проблема находится где-то здесь)

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

Теперь все это должно работать хорошо и хорошо. И на самом деле; Я могу архивировать/разблокировать некоторые из моих объектов. Архивы выглядят прекрасно, насколько я готов проверить их в шестнадцатеричном редакторе, и я могу разблокировать некоторые из моих пользовательских классов, содержащих NSMutableArray s какого-либо другого класса, содержащего double s.


Но по какой-то причине, если я пытаюсь разархивировать один мой объект, содержащий NSMutableArray из NSNumber, я бегу в эту проблему:

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

Моя ошибка была связана с неправильным пониманием второго аргумента -(void)encodeValueOfObjCType:(const char *)type at:(const void *)addr в случае, когда type представляет собой C-строку (*type == '*').В этом случае addr является const char **, указателем на const char *, который сам указывает на константу, 0 завершенный массив char, который должен быть закодирован.

NSNumber encodeWithCoder: кодирует небольшой C-строку, представляющую тип переменной подложки значение (в i для int, d для двойной и т.д. оно равно AFAIK к @encode директивы).

Моя предыдущая ошибочной интерпретации (при условии, addr была const char *) сделала неправильное кодирование/декодирование, и initWithCoder: был таким образом, не (нижняя строки: он пытается free переменную стек, таким образом, сообщения об ошибке, и тот факт, что адрес всегда было одинаковым для каждого вызова функции).

У меня теперь есть рабочая реализация. Если кому-то интересно, код находится на моем GitHub по лицензии MIT.