2015-03-24 9 views
8

Ho deciso di utilizzare NSSecureCoding su NSCoding, ma ho problemi a farlo funzionare.Enforcing types with NSSecureCoding

Mi aspetto che il seguente codice non funzioni, poiché sto codificando uno NSString ma sto tentando di decodificare uno NSNumber. L'oggetto viene inizializzato senza generare un'eccezione, tuttavia.

+ (BOOL)supportsSecureCoding 
{ 
    return YES; 
} 

- (instancetype)initWithCoder:(NSCoder *)coder 
{ 
    // prints '1' as expected 
    NSLog(@"%d", coder.requiresSecureCoding); 

    // unexpectedly prints 'foo' (expecting crash) 
    NSLog(@"%@", [coder decodeObjectOfClass:NSNumber.class forKey:@"bar"]); 

    return [super init]; 
} 

- (void)encodeWithCoder:(NSCoder *)coder 
{ 
    [coder encodeObject:@"foo" forKey:@"bar"]; 
} 

Ecco il codice che sto usando per testare il frammento di cui sopra:

MyClass *object = [[MyClass alloc] init]; 

NSMutableData *const data = [[NSMutableData alloc] init]; 
NSKeyedArchiver *const archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; 
archiver.requiresSecureCoding = YES; 

[archiver encodeObject:object forKey:@"root"]; 
[archiver finishEncoding]; 

NSKeyedUnarchiver *const unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; 
unarchiver.requiresSecureCoding = YES; 

[unarchiver decodeObjectOfClass:MyClass.class forKey:@"root"]; 
[unarchiver finishDecoding]; 

mi sto perdendo qualcosa di completamente ovvia o perché non fa eccezione lanciata durante la decodifica?

+0

i vostri NSLogs stampano effettivamente i valori? – Chase

+0

Sì, loro stampano quello che dice il commento sopra. –

+1

Il problema persiste se si chiama '[super initWithCoder:]' invece di '[super init]'? –

risposta

9

Considerando la definizione di -[NSCoder decodeObjectOfClass:forKey:], sì, il codice di esempio deve avere generato un'eccezione. La descrizione del metodo lo dice:

Decodifica un oggetto per la chiave, limitato alla classe specificata.

E la discussione dice:

Se il coder risponde YES-requiresSecureCoding, quindi un'eccezione saranno gettati se la classe da decodificare non implementa NSSecureCoding o non è isKindOfClass: di Aclass.

Ci sono due incongruenze con l'implementazione di questo metodo, relativa alle ottimizzazioni eseguite da NSKeyedUnarchiver. Il primo è che decodeObjectOfClass:forKey: e decodeObjectForKey: decodificano un oggetto solo la prima volta che lo si incontra.

Ad esempio, l'asserzione nel codice seguente passa perché foo e foo2 iniziato come lo stesso oggetto e sono stati decodificati solo una volta mentre foo3 iniziato come un oggetto separato e come risultato decodificato separatamente.

func encodeWithCoder(coder:NSCoder) { 
    let foo = NSSet(objects: 1, 2, 3) 
    coder.encodeObject(foo, forKey: "foo") 
    coder.encodeObject(foo, forKey: "foo2") 
    coder.encodeObject(NSSet(objects: 1, 2, 3), forKey: "foo3") 
} 

required init(coder: NSCoder) { 
    let foo = coder.decodeObjectOfClass(NSSet.self, forKey: "foo") 
    let foo2 = coder.decodeObjectOfClass(NSSet.self, forKey: "foo2") 
    let foo3 = coder.decodeObjectOfClass(NSSet.self, forKey: "foo3") 
    assert(foo === foo2) 
    assert(foo !== foo3) 
    super.init() 
} 

Sembra che le classi vengano controllate solo quando l'oggetto viene effettivamente decodificato. L'elenco delle classi approvate viene confrontato con la classe richiesta dall'oggetto. Così, nel mio esempio precedente, potrei cambiare la classe per foo2 ad essere quello che voglio e il codice sarà ancora correre e restituire un NSSet:

required init(coder: NSCoder) { 
    let foo = coder.decodeObjectOfClass(NSSet.self, forKey: "foo") 
    let foo2 = coder.decodeObjectOfClass(NSMutableDictionary.self, forKey: "foo2") 
    assert(foo === foo2) 
    super.init() 
}

La seconda incongruenza, legati direttamente al tuo esempio, è che certo oggetto i tipi non vengono mai decodificati. NSKeyedArchiver memorizza tutti i suoi dati come un elenco di proprietà binarie, che in base a Apple's source code dispone del supporto nativo per stringhe, dati, numeri, date, dizionari e tipi di array. Quando NSKeyedArchiver incontra un oggetto NSString, NSNumber o NSData (ma non una sottoclasse), invece di codificarlo utilizzando encodeWithObject: e salvando le informazioni su come decodificarlo, memorizza semplicemente il valore direttamente in PList. Quindi quando chiami decodeObjectOfClass:withKey: vede la stringa già presente e la restituisce immediatamente senza decodifica. Nessuna decodifica significa nessun controllo di classe.

Se questo comportamento è buono o cattivo potrebbe essere discusso.Meno controllo significa codice più veloce ma il comportamento in realtà non corrisponde alla documentazione dell'API. Detto questo, potresti chiederti che codice di sicurezza ti ottiene se non garantisce i tipi di reso. La codifica sicura con NSKeyedUnarchiver ti protegge da ciò che un archivio pericoloso non può farti chiamare alloc/initWithCoder: su una classe arbitraria. Se si desidera altro, è possibile creare una sottoclasse che convalida il tipo di output di tutte le chiamate decodeObjectOfClass:withKey: e decodeObjectOfClasses:withKey:.

+0

Grazie per la tua spiegazione dettagliata. Peccato che l'implementazione di Apple non sia coerente con la documentazione. –