2013-12-11 11 views
6

NSArchiver è deprecato dal OS X 10.2, e non è disponibile per quanto ne so su iOSSubclassing NSCoder, ricreando NSArchiver

D'altra parte, NSKeyedArchiver è noto per essere carente da parte concisione velocità & (some users rapporto più di 100 volte differenza di prestazioni tra NSKeyedArchiver e NSArchiver). Gli oggetti che voglio archiviare sono principalmente sottoclassi NSObject sottoclasse contenenti NSMutableArray di NSNumber e oggetti contenenti tipi primitivi (principalmente double). Non sono convinto che valga la pena archiviare l'overhead key archiviato.

Così ho deciso di sottoclasse NSCoder su iOS per creare un codificatore seriale, nello stile di NSArchiver.

Capisco dove potrebbero essere utili gli archivi con chiave: compatibilità con le versioni precedenti e altre sottigliezze, ed è probabilmente quello che finirò per usare, ma sarei curioso di sapere che tipo di prestazioni potrei ottenere con l'archiviazione seriale . E francamente, penso che potrei imparare un sacco di cose facendo questo. Quindi sono non interessato a una soluzione alternativa;

Sono stato ispirato dalle fonti di Cocotron, fornendo un open source NSArchiver

TLDR: voglio sottoclasse NSCoder per ricostruire NSArchiver


Sto usando ARC, la compilazione di iOS 6 & 7, e supponendo sistema a 32 bit per ora.

io non sono interessato a riferimento a oggetti o stringhe, per ora, sto solo usando un NSHashTable (weakObjectsHashTable) per evitare che i nomi di classi da duplicare: classi saranno descritte la prima volta che si incontrano, e poi indicati con riferimento .

Sto usando un NSMutableData per costruire l'archivio:

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

I metodi di base sono:

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

Sto usando _appendBytes:length: per fare uscire i tipi primitivi, come int, char, float, double ... ecc. Niente di interessante lì.

stringhe C-style vengono scaricati usando questo metodo altrettanto poco interessante:

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

} 

E, infine, le informazioni di classe archiviazione e oggetti:

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

} 

I encodeWithCoder: metodi di miei oggetti sembrano tutti così, niente di speciale:

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

La decodifica va più o meno allo stesso modo; L'unarchiver detiene un NSMapTable di classi che già conosce e cerca il nome di classi di riferimento che non conosce.

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

@end 

io non voglio annoiarvi con le specifiche di

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

e

-(char*)_extractCString 

La roba interessante è probabilmente nel codice oggetto di decodifica:

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

} 

E infine, il metodo centrale (I w Non ould stupitevi se il problema è da qualche parte qui)

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


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

Il metodo initWithCoder: corrispondente al codice precedente di encodeWithCoder: sarebbe andato qualcosa di simile

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

mio decodeObject implementazione è esattamente _extractObject.

Ora, tutto questo dovrebbe funzionare bene e bene. E in effetti; Sono in grado di archiviare/annullare l'archiviazione di alcuni dei miei oggetti. Gli archivi hanno un bell'aspetto, nella misura in cui sono disposto a ispezionarli in un editor esadecimale, e sono in grado di annullare l'archiviazione di alcune delle mie classi personalizzate contenenti NSMutableArray s di un'altra classe contenente double s.


Ma per qualche ragione, se provo a decomprimere un mio oggetto contenente un NSMutableArray di NSNumber, mi imbatto in questo problema:

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

Sembra che ci sia una linea per NSNumber nel array e l'indirizzo 0xc112cc è uguale per ogni linea. L'inserimento di un punto di interruzione in malloc_error_break indica che gli errori sono da -[NSPlaceholderNumber initWithCoder:] (richiamati dal metodo _extractObject).

È un problema legato al mio utilizzo di ARC? Cosa mi manca?

+0

Potrebbe condividere un initWithCoder minima: implementazione? – James

+0

Ho aggiornato il mio post con un'implementazione di esempio 'initWithCoder:'. È abbastanza semplice. – Olotiar

+0

che possa avere qualcosa a che fare con il puntatore del tag, spunta è '0xc112cc' il valore effettivo si sta archiviando? –

risposta

2

Il mio errore è stato collegato a un fraintendimento del secondo argomento di -(void)encodeValueOfObjCType:(const char *)type at:(const void *)addr nel caso in cui type rappresenti una stringa C (*type == '*').In tal caso, addr è un const char **, un puntatore allo const char *, che è esso stesso punta alla costante, 0 array terminato di char che dovrebbe essere codificato.

NSNumber encodeWithCoder: codifica una piccola C-stringa che rappresenta il tipo di supporto variabile il valore (a i per int, d per letto, ecc è uguale AFAIK alla direttiva @encode).

mio errata interpretazione precedente (assumendo addr era un const char *) realizzato un errato codifica/decodifica, e il initWithCoder: stato quindi fallendo (linea inferiore: si cercava di free una variabile di stack, quindi il messaggio di errore e il fatto che l'indirizzo era sempre lo stesso per ogni chiamata della funzione).

Ora ho un'implementazione di lavoro. Se qualcuno è interessato, il codice è il mio GitHub sotto la licenza MIT.