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?
Potrebbe condividere un initWithCoder minima: implementazione? – James
Ho aggiornato il mio post con un'implementazione di esempio 'initWithCoder:'. È abbastanza semplice. – Olotiar
che possa avere qualcosa a che fare con il puntatore del tag, spunta è '0xc112cc' il valore effettivo si sta archiviando? –