(Il materiale originale da cui è stato tratto questo post era in origine il Capitolo 14 del libro Objective-C Pocket di recente uscita)
La sintassi di Objective-C per l’accesso alle proprietà è molto semplice e consente di esprimere codice compatto e autodescrittivo. Per esempio, per accedere alla proprietà nome dell’oggetto Persona è possibile scrivere come segue:
Persona *p = [[Persona alloc] init];
p.nome = @"Enrico";
Con la codifica chiave-valore è possibile considerare l’insieme di proprietà di un oggetto come un dizionario, una struttura dati che contiene un insieme di oggetti, ciascuno dei quali è identificato in modo univoco da una chiave.
Per assegnare un valore alla proprietà nome utilizzando la codifica chiave-valore è necessario utilizzare il metodo setValue:forKey:. Il codice di cui sopra è implementabile quindi anche come segue:
Persona *p = [[Persona alloc] init];
[p setValue: @"Enrico" forKey: @"nome"];
Utilizzando questo metodo si imposta il valore della proprietà indicata nel parametro forKey con il valore passato come primo parametro. Se invece si desidera leggere il valore della proprietà, si può utilizzare il metodo valueForKey che si aspetta come parametro la chiave (il nome della proprietà) e ne restituisce il valore.
Per esempio, il codice seguente stampa nei log il valore delle proprietà nome e cognome:
NSLog(@"Nome=%@", [p valueForKey:@"nome"]);
NSLog(@"Cognome=%@", [p valueForKey:@"cognome"]);
Ma a cosa serve questa ulteriore modalità d’accesso alle proprietà? Il codice non risulta più compatto di quello normalmente utilizzato per accedere alle proprietà, né appare particolarmente elegante. Infatti, la codifica chiave-valore non offre il meglio di sé se utilizzata da sola, ma è una caratteristica abilitante per altre tecnologie, come l’osservazione chiave-valore, i collegamenti con elementi visuali e Core Data.
Dal punto di vista tecnico, i metodi relativi alla codifica chiave-valore sono definiti nel protocollo informale NSKeyValueCoding e una implementazione di default è presente nella classe NSObject.
Chiavi e dizionari
Nelle righe precedenti sono stati citati i dizionari, presentati come stretti parenti della codifica chiave-valore. Questa struttura dati è implementata in Cocoa dalla classe NSDictionary.
Oltre a una certa similitudine, NSKeyValueCoding e NSDictionary hanno un’attinenza a livello di API, dato che è possibile riversare il contenuto di un dizionario nelle proprietà di un oggetto oppure estrarre queste ultime per ottenere un’istanza di NSDictionary.
L’operazione più semplice è la prima e si basa su un metodo di NSKeyValueConding che si chiama setValuesForKeysWithDictionary. Il metodo si attende come parametro un dizionario, i cui valori sono utilizzati per valorizzare le proprietà dell’oggetto. Ovviamente sono utilizzate solo le chiavi del dizionario per cui esiste una corrispondenza con le proprietà dell’oggetto.
Il codice che segue utilizza questo metodo per valorizzare un oggetto Persona:
NSArray *objs = [NSArray
arrayWithObjects:@"Mario", @"Rossi", nil];
NSArray *keys = [NSArray
arrayWithObjects:@"nome", @"cognome", nil];
NSDictionary *dict = [NSDictionary
dictionaryWithObjects:objs forKeys:keys];
Persona *p = [[Persona alloc] init];
[p setValuesForKeysWithDictionary:dict];
NSLog(@"Nome=%@", [p valueForKey:@"nome"]);
NSLog(@"Cognome=%@", [p valueForKey:@"cognome"]);
Il dizionario creato ha il seguente contenuto:
{
cognome = Rossi;
nome = Mario;
}
L’output del programma è il seguente:
KeyValue[4246:a0f] Nome=Mario
KeyValue[4246:a0f] Cognome=Rossi
L’operazione inversa è possibile attraverso il metodo dictionaryWithValuesForKeys, ma è necessario passare a quest’ultimo un vettore con le chiavi da estrarre.
Nel codice che segue è presente un esempio: viene creato un oggetto Persona e un vettore di chiavi che contiene i valori nome e cognome. Quindi viene invocato il metodo dictionaryWithValuesForKeys e stampato il risultato:
Persona *p = [[Persona alloc] init];
p.nome = @"Giuseppe";
p.cognome = @"Verdi";
NSArray *keys = [NSArray
arrayWithObjects:@"nome", @"cognome", nil];
NSDictionary *dict =
[p dictionaryWithValuesForKeys:keys];
NSLog(@"%@", [dict description]);
L’output del programma è il seguente:
KeyValue[4355:a0f] {
cognome = Verdi;
nome = Giuseppe;
}
Nel caso il dizionario passato a setValuesForKeysWithDictionary o il vettore passato dictionaryWithValuesForKeys a non contengano chiavi per cui non sono presenti proprietà, viene sollevata una NSException (il cui nome è NSUnknownKeyException e la reason vale this class is not key value coding-compliant for the key XXX).
Valori mancanti
Le proprietà di un oggetto possono assumere il valore nil, la costante che indica che una variabile di tipo riferimento a oggetto non contiene alcun valore. Il problema è che i dizionari non possono contenere chiavi il cui valore è nil. Questo potrebbe creare confusione nel momento in cui si utilizzino i metodi sopra indicati per passare da proprietà a dizionari e viceversa.
In oggetti NSDictionary i valori nil vengono rappresentati da un oggetto specifico e molto semplice. Presenta infatti pochissimi metodi e campi perché è sostanzialmente una classe che rappresenta un concetto. Si tratta di NSNull.
I metodi setValuesForKeysWithDictionary e dictionaryWithValuesForKeys eseguono in modo autonomo la conversione tra nil e NSNull, quindi nelle proprietà degli oggetti si troverà, dove opportuno, il valore nil, mentre nelle collezioni NSNull.
La codifica chiave valore non cerca solo le proprietà con la chiave richiesta, ma anche le variabili d’istanza. Quando si utilizza il metodo valueForKey o setValue:forKey:, la ricerca della dato a cui accedere avviene cercando prima la presenza nella classe di un metodo accessor adeguato (per esempio, per la proprietà nome vengono cercati getNome, nome e isNome, dato che la proprietà potrebbe essere di tipo booleano e quindi si sarebbe potuto utilizzare un getter dal nome personalizzato). Se questo non viene trovato vengono cercate le collezioni (percorsi); se anche questa ricerca non produce risultati e il flag accessingInstanceVariablesDirectly dell’oggetto vale YES, vengono cercate le variabili (nell’ordine, per la chiave nome il runtime cerca _nome, _isNome, nome e IsNome).
Fai un fischio ai tuoi amici: