Una delle caratterestiche più importanti di Objective-C di cui abbiamo già parlato in un precedente articolo sono le Categorie. Esse permettono di aggiungere metodi arbitrari ad una classe di cui, ad esempio, non si ha il codice sorgente, specializzandola secondo le nostre esigenze.
Alcune volte risulta necessario aggiungere anche delle variabili d’istanza alla classe specializzata con le Categorie. In linea di principio questo non è possibile e probabilmente indica una cattiva ingegnerizzazione della classe su cui stiamo lavorando. Tuttavia, le referenze associative ci permettono di aggirare questa limitazione, fornendo uno strumento per aggiungere oggetti (variabili d’istanza) a runtime ad un altro oggetto disponibile durante l’esecuzione della nostra applicazione. Essenzialmente, questo significa che ogni oggetto ha un dizionario nascosto/opzionale a cui è possibile aggiungere delle coppie chiave/valore.
Quest’articolo illustra le tecniche base per poter trarre profitto da questa caratteristica, illustrando due tipici casi d’uso.
Le Referenze Associative
Le referenze associative, disponibili a partire da Mac OS X 10.6 ed iOS 3.1, permettono l’associazione di uno o più oggetti ad un’altro oggetto istanziato. Utilizzando le referenze associative, quindi, è possibile “attaccare” uno o più oggetti ad un altro oggetto senza modificarne la dichiarazione di classe. Questo pattern potrebbe ritornare molto utile quando si lavora, ad esempio, con librerie terze di cui non abbiamo il codice sorgente o quando la categorizzazione della classe necessita dell’uso di una o più variabili d’istanza.
Le associazioni sono basate sul concetto di chiave. E’ possibile aggiungere ad ogni oggetto un numero arbitrario di associazioni, ognuna con una chiave identificativa diversa.
Creiamo un’associazione
La funzione messa a disposizione da Objective-C per creare un’associazione tra un oggetto ed un altro è objc_setAssociatedObject. La funzione prende quattro parametri: l’oggetto sorgente, una chiave, l’oggetto da associare ed una costante che indica il tipo di associazione.
void objc_setAssociatedObject( id object,
void *key,
id value,
objc_AssociationPolicy policy);
Riguardo ai parametri object e value, rispettivamente oggetto che riceve l’associazione ed oggetto da associare, credo ci sia molto poco da chiarire. Quello che invece merita una particolare attenzione sono i parametri key e policy:
- key è un puntatore void, unico per ogni associazione. Il pattern tipico,
consigliato da Apple, è quello di usare una variabile statica. Qualcosa del tipo:static char *KEY_IDENTIFIER;
andrà bene per lo scopo che ci siamo preposti. Ricordate che la funzione prende
come parametro un puntatore all’indirizzo di memoria del CARATTERE. - policy specifica come viene assegnato l’oggetto associato (retain, copy, nonatomic,atomic). I tipi possibili sono definiti in un enum riportato di seguito:
enum { OBJC_ASSOCIATION_ASSIGN = 0, OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, OBJC_ASSOCIATION_COPY_NONATOMIC = 3, OBJC_ASSOCIATION_RETAIN = 01401, OBJC_ASSOCIATION_COPY = 01403 };
Valgono le stesse regole esistenti per @property/@synthesize. Quindi, un oggetto trattenuto (retained) avrà il suo reference counter incrementato almeno per tutta la durata di vita dell’oggetto a cui è stato assegnato; assign, permetterà di effettuare un’associazione debole, copy creerà una copia dell’oggetto passato. L’uso dei parametri NONATOMIC/ATOMIC, permette di dialogare con circostanze in cui si renda necessario l’uso di concorrenza o meno. Per maggiori informazioni su questo argomento, vi rimando alla documentazione Apple.
Recuperiamo un valore associato
Per recuperare un valore associato precedentemente ci viene in aiuto la funzione objc_getAssociatedObject. La funzione prende due parametri: l’oggetto sorgente da cui si intende recuperare e la chiave dell’oggetto da recuperare.
Ricordate che ad ogni chiave (la variabile statica di cui sopra) può essere associato uno ed un solo valore. Se intendiamo associare più di un oggetto con questo metodo, avremo bisogno di più chiavi dichiarate in forma statica. Approfondiremo questo concetto tra un attimo, quando scriveremo il nostro primo esempio. Il prototipo della funzione di get è il seguente:
void objc_getAssociatedObject( id object, void *key);
Un esempio concreto
Adesso che abbiamo chiaro cosa sono e come si comportano le referenze associative, proviamo a creare un esempio completo che utilizza questa caratteristica. Per iniziare, creeremo una categoria che specializza la classe UIView standard, fornendo un modo per aggiungere un tag arbitrario a questa classe.
Nota: La proprietà tag, pre-esistente nell’oggetto UIView ed ereditata da NSObject, permette già di associare interi. Questo potrebbe risultare limitativo in molti casi pratici in cui si
necessita di tagging più complesso. La categoria creata in quest’esempio fornisce un metodo risolutivo a questa limitazione.
Apriamo il nostro XCode e creiamo una nuova “Empty Application”. Per prima cosa, occupiamoci di creare una nuova categoria che specializzerà la classe preesistente UIView. Premiamo ⌘+N e scegliamo di creare una nuova categoria:

Diamo un nome alla nostra categoria, prestando attenzione che la classe da cui ereditiamo sia impostata in UIView.

A questo punto modifichiamo il file di dichiarazione aggiungendo una proprietà di tipo generico id con cui dialogheremo attraverso l’uso delle referenze associative:
#import
@interface UIView (TagObject)
@property (nonatomic, retain) id objectTag;
@end
Spostiamoci nel file di implementazione (.m) e modifichiamolo come segue:
#import
#import "UIView+TagObject.h"
static char *tagKey;
@implementation UIView (TagObject)
- (id)objectTag {
return objc_getAssociatedObject(self, tagKey);
}
- (void)setObjectTag:(id)newObjectTag {
objc_setAssociatedObject(self,
tagKey,
newObjectTag,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
La prima cosa a cui prestare attenzione è la nuova direttiva di compilazione inserita in testa al file. Per poter usare le primitive di referenza associativa è necessario includere il file di libreria responsabile di tutte le funzioni di runtime:
Specifichiamo adesso una variabile static di tipo puntatore a carattere, prima dell’apertura della dichiarazione di implementation. Così facendo, la variabile sarà preallocata in memoria dal nostro eseguibile e tutte le classi potranno trarne giovamento.
Nota: I più attenti di voi avranno notato che non è stato assegnato nessun valore alla variabile statica dichiarata. Nonostante questo strano comportamento, l’applicativo funziona correttamente ed il risultato è quello aspettato. Perchè?
A questo punto, quello che ci resta da fare è creare i due metodi di accesso alla proprietà dichiarata nel file di interfaccia. I metodi di set ed i metodi di get sopra, svolgono il compito richiesto.
La classe controllore
Una volta creata la categoria, spostiamo la nostra attenzione sul controllore di vista. Procediamo come da abitudine, creando una classe che eredita direttamente da UIViewController:

e chiamiamola MainViewController:

Modifichiamo il file di interfaccia (.h), aggiungendo una dichiarazione di inclusione per la nuova categoria creata ed aggiungiamo una variabile d’istanza denominata contentView. In questo modo, potremo dialogare con la categoria appena creata:
#import
#import "UIView+TagObject.h"
@interface MainViewController : UIViewController {
UIView *contentView;
}
@end
Spostiamoci sul file di implementazione ed aggiungiamo i metodi di inizializzazione e caricamento programmatico della view principale del controllore:
@implementation MainViewController
- (void)loadView
{
contentView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[contentView setBackgroundColor:[UIColor darkGrayColor]];
NSDictionary *aDictionary = [NSDictionary dictionaryWithObject:@"STOREDVALUE"
forKey:@"aKey"];
//set the Associative Reference to whatever you want...
[contentView setObjectTag:aDictionary];
...
}
Le uniche parti importanti di questo codice, sono quelle relative all’uso dei metodi setter e getter, creati appositamente per la nostra categoria. Ci basterà creare un’istanza di un NSDictionary ed associarla attraverso il metodo di set alla nuova property della nostra categoria. In questo modo, qualunque parte del codice che ha visibilità su contentView potrà accedere al nostro dizionario custom. A tal proposito, scriviamo il metodo responsabile della pressione sull’oggetto UIButton, istanziato in fase di loadView:
- (void)buttonDidPressed:(id)sender {
NSDictionary *aDictionary = [contentView objectTag];
// Do something usefull with this object...
...
}
Modifichiamo, infine, il file di AppDelegate, in maniera che possa caricare correttamente la nostra classe UIViewController, in fase di didFinishLaunching:
#import "AppDelegate.h"
#import "MainViewController.h"
@implementation AppDelegate
...
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
MainViewController *mainVC = [[[MainViewController alloc]
init] autorelease];
[self.window setRootViewController:mainVC];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
Compiliamo ed avviamo il nostro progetto. All’apertura, premendo sul tasto al centro dello schermo, recupereremo il valore di dizionario associato alla contentView, mostrandolo a schermo.
Questo comportamento è possibile grazie all’uso della categoria UIView che specializza la classe base, aggiungendo la possibilità di “taggare” l’oggetto standard, associandogli un oggetto custom qualsiasi.
Varianti sul tema e passaggio di parametri ad un selettore
L’uso delle referenze associative può andare ben oltre il nostro primo esempio, dimostrandosi essenziale nell’utilizzo di particolari pattern di programmazione. Una delle domande che mi viene rivolta più spesso durante i corsi di programmazione iOS è quella di come poter passare dei parametri ad un selettore
associato, ad esempio, alla pressione di un oggetto UIButton.
Riprendiamo il nostro esempio precedente e supponiamo che il selettore associato all’oggetto UIButton abbia necessità di ottenere dei valori dinamici in base a particolari stati dell’applicazione.
Modifichiamo il file di implementazione del MainViewController come segue:
static char UIB_PROPERTY_KEY;
...
@implementation MainViewController
...
- (void)loadView
{
...
objc_setAssociatedObject(aButton, &UIB_PROPERTY_KEY, aDictionary,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
...
}
Così facendo, abbiamo associato lo stesso dizionario precedentemente legato alla contentView, all’oggetto UIButton. Questo significa che, all’invocazione del selettore, avremo modo di leggere questo dizionario. Vediamo come:
- (void)buttonDidPressed:(id)sender {
...
NSDictionary *anotherDict = objc_getAssociatedObject(sender,
&UIB_PROPERTY_KEY);
...
}
Nota come è possibile accedere direttamente all’oggetto associato, passando come primo parametro il puntatore generico sender, passato al selettore.
A questo punto, il nostro selettore potrà reagire in maniera diversa, a seconda dei dati contenuti nel dizionario d’esempio. Le varianti sul tema e gli scenari di possibile applicazione di questa tecnica diventano infiniti ed “allegeriscono” la nostra classe di tante variabili d’istanza, spesso inutili.
Se avete problemi con il progetto presentato, questo è il link per scaricare il nostro esempio.

3 Responses to “T#107 – Le referenze associative: Aggiungiamo le variabili alle Categorie”
2 Aprile 2012
ignaziocottimo articolo, per veri “smanettoni”.
Le categorie sono uno strumento molto utile, soprattutto quando è sconsigliato subclassare (vedi nsstring) con questa tecnica sia ha una marcia in più.
3 Aprile 2012
Francesco Montroneche dire … grazie!
3 Aprile 2012
LubboA proposito della gestione della memoria? Bisogna ricordarsi di liberare gli oggetti settati con opzione retain quando non servono più, giusto?