2013-12-11 14 views
6

NSArchiver seit O X ist veraltet 10.2 und ist AFAIK nicht auf iOSSubclassing NSCoder, Neue NSArchiver

Auf der anderen Seite wird NSKeyedArchiver bekannt von der Geschwindigkeit & Prägnanz Teil zu fehlen (some users Bericht mehr als 100 mal Leistungsunterschied zwischen NSKeyedArchiver und NSArchiver). Die Objekte, die ich archivieren möchte, sind hauptsächlich NSObject Unterklassen, die NSMutableArray von NSNumber enthalten, und Objekte, die primitive Typen enthalten (hauptsächlich double). Ich bin nicht davon überzeugt, dass die Overhead-Keyed-Archivierung es wert ist.

Also habe ich beschlossen, NSCoder auf iOS unterteilen, um einen seriellen Coder im Stil von NSArchiver zu erstellen.

Ich verstehe, wo verschlüsselte Archive nützlich sein könnten: rückwärts Vorwärtskompatibilität und andere Feinheiten, und es ist wahrscheinlich, was ich am Ende verwenden werde, aber ich wäre gespannt, welche Art von Leistungen ich mit serieller Archivierung bekommen könnte . Und ehrlich gesagt denke ich, dass ich dadurch viele Dinge lernen könnte. So bin ich nicht interessiert an einer alternativen Lösung;

Ich habe von den Quellen von Cocotron inspiriert, eine Open-Source-Bereitstellung NSArchiver

TLDR: Ich möchte NSCoder Unterklasse NSArchiver


Ich bin mit ARC neu zu erstellen, für die Erstellung iOS 6 & 7 und 32bit-System für jetzt vorausgesetzt.

Ich bin in Referenzierung Objekte oder Zeichenfolgen für jetzt nicht interessiert, ich bin nur mit einem NSHashTable (weakObjectsHashTable) Klassen-Namen zu verhindern dupliziert werden: Klassen wird das erste Mal beschrieben sie angetroffen werden, und verweist dann auf die durch Bezugnahme .

ich ein NSMutableData bin mit dem Archiv zu erstellen:

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

Die grundlegenden Methoden sind:

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

ich _appendBytes:length: bin mit primitiven Typen dump wie int, char, float, double ... etc. Nichts Interessantes dort.

C-Strings mit dieser ebenso uninteressant Methode des Einbringens:

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

} 

Und schließlich Archivierungsklasse Informationen und Objekten:

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

} 

Die encodeWithCoder: Methoden meiner Objekte all das aussehen, nichts Besonderes:

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

Decoding geht ziemlich genau so; Der Unarchivierer enthält eine NSMapTable Klasse, die er bereits kennt und sucht nach dem Namen der Klassenreferenz, die er nicht kennt.

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

@end 

Ich will Sie nicht langweilen mit den Besonderheiten von

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

und

-(char*)_extractCString 

Das interessante Sachen ist wahrscheinlich in dem Objekt Decodierungscode:

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

} 

Und schließlich, die zentrale Methode (I w etwas wie die

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

Meiner decodeObject Implementierung gehen würde, ist genau _extractObject Ould nicht überrascht, wenn das Problem irgendwo hier)

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


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

Die initWithCoder: Methode zum vorherigen Snippet encodeWithCoder: entspricht.

Jetzt sollte das alles gut und schön funktionieren. Und tatsächlich; Ich kann einige meiner Objekte archivieren/entpacken. Die Archive sehen gut aus, soweit ich bereit bin, sie in einem Hex-Editor zu untersuchen, und ich kann einige meiner benutzerdefinierten Klassen, die NSMutableArray s einer anderen Klasse enthalten, die double enthält.


Aber aus irgendeinem Grund, wenn ich versuche, eine meiner Objekt aus dem Archiv entfernen eine NSMutableArray von NSNumber, enthält betreibe ich in dieses Problem:

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

Es scheint eine Zeile zu sein pro NSNumber in der Array, und die Adresse 0xc112cc ist für jede Zeile gleich. Setzen Sie einen Breakpoint in malloc_error_break sagt mir, dass der Fehler ist von -[NSPlaceholderNumber initWithCoder:] (von meinem _extractObject Methode aufgerufen).

Ist das ein Problem mit meiner Verwendung von ARC verbunden? Was vermisse ich ?

+0

Können Sie eine minimale initWithCoder: Implementierung teilen? – James

+0

Ich habe meinen Beitrag mit einer Beispiel-Implementierung von 'initWithCoder:' aktualisiert. Es ist ziemlich einfach. – Olotiar

+0

kann es etwas mit dem Tag-Zeiger zu tun haben, check ist '0xc112cc' der tatsächliche Wert, den Sie archivieren? –

Antwort

2

Mein Fehler war mit einem Missverständnis des zweiten Arguments von -(void)encodeValueOfObjCType:(const char *)type at:(const void *)addr in dem Fall verbunden, in dem type eine C-Zeichenfolge darstellt (*type == '*').In diesem Fall ist addr ein const char **, ein Zeiger auf die const char *, die selbst auf das konstante 0 terminierte Array von char zeigt, das codiert werden sollte.

NSNumber encodeWithCoder: codiert eine kleine C-String, der den Typ der Variablen Träger der Wert (ein i für int, d für Doppel usw. Es ist gleich AFAIK zum @encode Richtlinie).

Meine frühere Fehlinterpretation (vorausgesetzt addr war eine const char *) eine falsche Codierung/Decodierung gemacht, und die initWithCoder: somit versagt (untere Zeile: es wurde auf free einen Stapel variable versucht, wodurch die Fehlermeldung, und die Tatsache, dass die Adresse war immer gleich für jeden Aufruf der Funktion).

Ich habe jetzt eine funktionierende Implementierung. Wenn jemand interessiert ist, ist der Code auf meiner GitHub unter der MIT-Lizenz.