Le Categories sono uno strumento fondamentale per chi sviluppa in Objective-C; permettono di estendere una classe, senza effettuare il subclass, aggiungendo nuovi metodi. Il limite è rappresentato dal non poter aggiungere stored properties, così è definito nella documentazione di Apple, ma in realtà esiste un modo grazie alle caratteristiche (spesso sottovalutate) di Objective-C.
Il meccanismo che permette di superare questo limite delle categories è chiamato associated objects.
Come suggerisce il nome, si tratta di valori associati ad un altro oggetto utilizzando una chiave unica per memorizzarli e recuperarli, similmente a quanto accade per i dictionary.
È possibile ottenere e impostare valori tramite associated objects utilizzando le seguenti due funzioni di runtime e verranno automaticamente rilasciate quando l’oggetto con cui sono associate sarà deallocato:
1 2 3 4 5 |
// Recupera il valore associato ad un oggetto tramite una chiave id objc_getAssociatedObject(id object, const void *key) // Associa un valore all'oggetto tramite una chiave void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) |
object rappresenta l’oggetto a cui deve essere associato il valore,
key è la chiave univoca con cui associare il valore e
value è ovviamente il valore da associare.
L’argomento
policy rappresenta la modalità con cui il valore viene immagazzinato; si tratta di un
enum messo a disposizione da Objective-C con i seguenti valori:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/** * Policies related to associative references. * These are options to objc_setAssociatedObject() */ typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */ OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. * The association is not made atomically. */ OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied. * The association is not made atomically. */ OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object. * The association is made atomically. */ OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied. * The association is made atomically. */ }; |
Spiegato il funzionamento delle associated objects, segue un esempio di come poter aggiungere una stored property ad una category e nello specifico aggiungeremo l’attributo tag ad una UIImage .
File UIImage+Tag.h:
1 2 3 4 5 |
@interface UIImage (Tag) @property (nonatomic, copy) NSString *tag; @end |
File UIImage+Tag.m:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#import <objc/runtime.h> static const void *ImageTagKey = &ImageTagKey; @implementation UIImage (Tag) - (void)setTag:(NSSting *)tag { objc_setAssociatedObject(self, ImageTagKey, tag, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (NSString *)tag { return objc_getAssociatedObject(self, ImageTagKey); } @end |
Selettori e chiavi
Quando si devono definire molte stored property per una category, può essere noioso (e anche non particolarmente elegante dal punto di vista del codice) definire tutta una serie di chiavi per ogni proprietà.
Anche in questo caso Objective-C ci viene incontro e ci permette di ottimizzare e rendere più pulito e leggibile il codice in quanto i selettori in Objective-C (i nomi del metodo) sono in realtà constant pointers; questo significa che sono adatti per essere utilizzati come chiavi per gli oggetti associati. E’ possibile quindi utilizzare il nome del metodo getter della proprietà come chiave. Il file di implementazione diventerebbe quindi il seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#import <objc/runtime.h> @implementation UIImage (Tag) - (void)setTag:(NSSting *)tag { objc_setAssociatedObject(self, @selector(tag), tag, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (NSString *)tag { return objc_getAssociatedObject(self, @selector(tag)); } @end |