mercoledì 28 aprile 2010

Guida Objective-C in Italiano - Parte 6 - Le proprietà Dichiarate


In questa sesta parte della Guida alla programmazione in Objective-C parleremo delle proprietà dichiarate delle classi, come si dichiarano, come si implementano, come si usano e come si sintetizzano automaticamente i loro metodi accessori.

Questo post è una traduzione in italiano della Guida all'Objective C, presente sul sito developer.apple.com a questo link. L'Objective C è il linguaggio di programmazione per creare applicazioni per iPhone. Questo articolo può interessare a tutti gli sviluppatori che vogliono imparare le basi di questo linguaggio che è il fondamento per programmare nuove applicazioni per Mac Os X, iPhone ed iPad.


Proprietà Dichiarate

Le proprietà dichiarate Objective-C fornisce un semplice modo di dichiarare ed implementare i metodi accessori di un metodo.

Anteprima

Questi sono due aspetti di questa caratteristica del linguaggio: gli elementi sintattici che usi per specificare e sintetizzare le proprietà dichiarate, e un elemento sintattico collegato che sarà descritto in "Sintassi Punto".

Tipicamente accedi alle proprietà di un oggetto (i suoi attributi e relazioni) attraverso una coppia di metodi accessori (getter e setter). Usando i metodi accessori, aderisci al principio dell'incapsulamento. Puoi esercitare uno stretto controllo del comportamento della coppia getter/setter e della gestione dello stato di fondo mentre i clients delle API restano isolati dai cambiamenti dell'implementazione.

Sebbene usare metodi accessori abbia vantaggi significativi, scrivere metodi accessori è un processo noioso - in particolare se devi scrivere codice per supportare entrambi ambienti garbage collected e reference counted. Inoltre, aspetti delle proprietà che possono essere importanti per i clienti delle API sono lasciate oscurate - come se i metodi accessori fossero thread-safe o se nuovi valori sono copiati quando si impostano.

Le proprietà dichiarate indirizzano i problemi con metodi accessori standard fornendo le seguenti caratteristiche:
  • La dichiarazione di proprietà fornisce una chiara, ed esplicita specifica di come i metodi accessori si comportano.
  • Il compilatore può sintetizzare i metodi accessori per te, in accordo alla specifica da te fornita nella dichiarazione. Questo significa che hai meno codice da scrivere e mantenere.
  • Le proprietà sono rappresentate sintatticamente come identificatori e hanno uno scope, quindi il compilatore può identificare un uso di proprietà non dichiarate.

Dichiarazione di Proprietà e Implementazione

Ci sono due parti di una proprietà dichiarata, la sua dichiarazione e la sua implementazione.

Dichiarazione di Proprietà

Una dichiarazione di proprietà inizia con la parola chiave @property. @property può apparire ovunque nella lista della dichiarazione dei metodi trovata nella @interface di una classe. @property può anche apparire nella dichiarazione di un protocollo o di una categoria.

@property(attributes) type name;

@property dichiara una proprietà. Una insieme opzionale di attributi tra parentesi, fornisce dettagli aggiuntivi riguardo alle semantiche di memoria e altri comportamenti della proprietà. Come ogni altro tipo Objective-C, ogni proprietà ha una specifica di tipo ed un nome.

Il listato 5-1 illustra la dichiarazione di una semplice proprietà.

Listato 5-1 Dichiarazione di una semplice proprietà
@interface MyClass : NSObject

{

float value;

}

@property float value;

@end

Puoi pensare che una dichiarazione di proprietà sia l'equivalente a dichiarare due metodi accessori. Così
@property float value;
è equivalente a:
- (float)value;

- (void)setValue:(float)newValue;

Una dichiarazione di proprietà comunque, fornisce informazioni aggiuntive su come i metodi accessori sono implementati, come descritto in Attributi di dichiarazioni di proprietà.

Attributi di Dichiarazioni di Proprietà

Potete decorare una proprietà con degli attributi usando la forma @property(attribute [, attribute2, ...]). Come i metodi, le proprietà hanno lo scope nella dichiarazione di interfaccia che le racchiude. Per le dichiarazioni di proprietà che usano una lista di nomi di variabili delimitata da virgole, gli attributi di proprietà si applicano a tutte le proprietà nominate.

Se usi la direttiva @synthesize per dire al compilatore di creare i metodi accessori, il codice che genera combacerà con la specifica data dalle parolechiave. Se implementi i metodi accessori per conto tuo, dovresti appunto assicurarti che combacino con la specifica (ad esempio, se specifichi "copy", ti devi assicurare che effettivamente copi il valore di input nel metodo setter).

Nomi dei Metodi Accessori

I nomi di default per i metodi getter e setter associati ad una proprietà, sono "nomeProprietà" e "setNomeProprietà:" rispettivamente - ad esempio, avendo una proprietà "foo", gli accessori sarebbero foo e setFoo:. Gli attributi seguenti permettono invece di specificare nomi personalizzati. Sono entrambi opzionali e potrebbero apparire con qualunque altro attributo (tranne per readonly nel caso di setter=).

getter=getterName
Specifica il nome del metodo accessorio get per la proprietà. Il getter deve restituire un tipo che combacia con il tipo della proprietà e non prendere argomenti.

setter=setterName
Specifica il nome del metodo accessorio set per la proprietà. Il metodo setter deve prendere un singolo argomento il cui tipo combaci con il tipo della proprietà e deve restituire void.
Se specifichi che una proprietà è readonly allora specifichi anche un setter con setter=, otterrai un warning dal compilatore.

Tipicamente dovresti specificare i nomi dei metodi accessori che sono chiave-valore codifica compiacente - una ragione comune per usare il decoratore getter è di aderire alla convenzione isNomeProprietà per valori booleani.

Scrivibilità

Questi attributi specificano se una proprietà ha un metodo accessorio set associato o meno. Sono mutuamente esclusivi.

readwrite
Indica che la proprietà dovrebbe essere trattata come read/write. Questa è l'opzione di default.
Entrambi i metodi getter e setter saranno richiesti nella @implementation. Se usi @synthesize nel blocco implementazione, i metodi getter e setter saranno sintetizzati.

readonly
Indicache la proprietà è read-only.
Se specifichi readonly, sarà richiesto solo un metodo getter nella @implementation. Se usi @synthesize nel blocco dell' implementazione, sarà sintetizzato solo il metodo getter. Inoltre, se provi ad assegnare un valore usando la sintassi punto, ottieni un errore di compilazione.

Semantica Setter

Questi attributi specificano le semantiche di un accessorio set. Sono mutualmente esclusive.

assign
Specifica che il setter usa un assegnamento semplice. Questo è di default.
Tipicamente usi questo attributo per tipi scalari come NSInteger e CGRect, o (in un ambiente reference-counted) per oggetti di cui non sei il proprietario come i delegati.

retain ed assign sono praticamente la stessa cosa in ambienti garbage-collected.

retain
Specifica che dovrebbe essere invocato retain sull'oggetto sotto assegnamento. (Di default è assign.) Il valore precedente è inviato a un messaggio release.
Prima di Mac OS X v10.6, questo attributo era valido solo per tipi di oggetti Objective-C (quindi non puoi specificare retain per oggetti di Core Foundation).

Su Mac OS X v10.6 e successivi, puoi usare __attributo__ parolachiave per specificare che una proprietà del Core Foundation dovrebbe essere trattata come un oggetto Objective-C per la gestione della memoria, come illustrato in questo esempio:
@property(retain) __attribute__((NSObject)) CFDictionaryRef myDictionary;

copy
Specifica che una copia dell'oggetto dovrebbe essere usata per l'assegnamento (di default è assign). Il valore precedente è inviato a un messaggio release.
La copia è fatta invocando il metodo copy. Questo attributo è valido solo per tipi di oggetti, che devono implementare il protocolo NSCopying.

Si applicano vincoli differenti a seconda di se si usa la garbage collection o no:
  • Se non usi la garbage collection, per le proprietà degli oggetti devi esplicitamente specificare uno tra assign, retain o copy - altrimenti avrai un warning dal compilatore. (Questo ti incoraggia a pensare su che tipo di comportamento vuoi per la gestione della memoria e a digitarlo esplicitamente). Per decidere quale dovresti scegliere, devi capire le politiche di gestione memoria di Cocoa (Vedi Memory Management Programming Guide for Cocoa).
  • Se usi la garbage collection, non otterrai un warning se usi il default (che è, se non specifichi nulla tra assign, retain o copy) a meno che il tipo della proprietà sia una classe conforme ad NSCopying. Il default è di solito quello che vuoi; se il tipo della proprietà può essere copiato, comunque, per preservare l'incapsulamento spesso vorrai fare una copia privata dell'oggetto.

Atomicità

Questo attributo specifica che i metodi accessori non sono atomici. (non c'è una parola chiave per denotare atomico).

nonatomic
Specifica che i metodi accessori sono non-atomici. Di default, i metodi accessori sono atomici.
Le proprietà sono atomiche di default quindi i loro metodi accessori sintetizzati forniscono un accesso robusto alle proprietà in un ambiente multi-threaded - che è, il valore restituito dal getter o settato tramite il setter è sempre pienamente recuperato o impostato senza curarsi di quali altri threads sono concorrentemente in esecuzione.

Se non specifichi nonatomic, allora in un ambiente reference counted, un metodo accessorio sintetizzato per una proprietà di un oggetto usa un lock e conserva e autorilascia il valore restituito - l'implementazione sarà simile alla seguente:
[_internal lock]; // lock using an object-level lock

id result = [[value retain] autorelease];

[_internal unlock];

return result;

Se specifichi nonatomic, allora un accessorio sintetizzato per una proprietà di un oggetto semplicemente restituisce il valore direttamente.

Markup e Deprecazione

Le proprietà supportano la piena gamma di decoratori di stile C. Le proprietà possono essere deprecate e supportare attributi di markup di stile __attribute__ , come illustrato nel seguente esempio:
@property CGFloat x

AVAILABLE_MAC_OS_X_VERSION_10_1_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_4;

@property CGFloat y __attribute__((...));

Se vuoi specificare che una proprietà è un'outlet di Interface Builder, puoi usare l'identificatore IBOutlet:
@property (nonatomic, retain) IBOutlet NSButton *myButton;
IBOutlet non è una parte formale della lista degli attributi.

Se usi la garbage collection, puoi usare modificatori di storage come __weak e __strong in una dichiarazione di proprietà:
@property (nonatomic, retain) __weak Link *parent;
ma non sono una parte formale della lista degli attributi.

Direttive di Implementazione di Proprietà

Puoi usare le direttive @synthesize e @dynamic nei blocchi @implementation per innescare specifiche azioni del compilatore. Nota che nessuna delle due è richiestaper qualunque dichiarazione di @property data.

Importante: se non specifichi nè @synthesize nè @dynamic per una particolare proprietà, devi fornire un'implementazione dei metodi getter e un setter (o solo du un getter nel caso di una proprietà readonly) per questa proprietà.

@synthesize
Usi la parola chiave @synthesize per dire al compilatore che dovrebbe sintetizzare i metodi setter e/o getter per la proprietà se non li fornisci nel blocco @implementagion.

Listato 5-2 Usare @synthesize
@interface MyClass : NSObject

{

NSString *value;

}

@property(copy, readwrite) NSString *value;

@end



@implementation MyClass

@synthesize value;

@end

Puoi usare la forma property=ivar per indicare che una particolare variabile di istanza dovrebbe essere usata per la proprietà, ad esempio:
@synthesize firstName, lastName, age = yearsOld;
Questo specifica che i metodi accessori per firstName, lastName, e age dovrebbero essere sintetizzati e che la proprietà age è rappresentata dalla variabile di istanza yearsOld.

Se specifichi o no il nome della variabile di istanza, @synthesize può solo usare una variabile di istanza dalla classe corrente, non una superclasse.

Ci sono differenze nel comportamento che dipendono dal runtime:
  • Per i runtime a eredità, le variabili di istanza devono già essere dichiarate nel blocco @interface della classe corrente. Se una variabile di istanza dello stesso nome e di tipo compatibile a quello della proprietà esiste, è usata - altrimenti, otterrai un errore di compilazione.
  • Per i runtime moderni, le variabili di istanza sono sintetizzate come serve. Se esiste già una variabile di istanza con lo stesso nome, è usata.

@dynamic
Si usa la parola chiave @dynamic per dire al compilatore che adempirete al contratto delle API implicito da una proprietà o fornendo implementazioni di metodi direttamente, o a runtime usando altri meccanismi come il caricamento dinamico del codice o risoluzione di metodo dinamico. L'esempio mostrato nel listato 5-3 illustra l'uso delle implementazioni dirette di metodo - equivalente all'esempio del listato 5-2.

Listato 5-3 Usare @dynamic con implementazioni dirette di metodo
@interface MyClass : NSObject

{

NSString *value;

}

@property(copy, readwrite) NSString *value;

@end



// assume using garbage collection

@implementation MyClass

@dynamic value;



- (NSString *)value {

return value;

}



- (void)setValue:(NSString *)newValue {

if (newValue != value) {

value = [newValue copy];

}

}

@end


Usare le Proprietà

Tipi Supportati

Puoi dichiarare una proprietà per qualunque classe Objective-C, tipo di dati Core Foundation o tipi "plain old data" (POD).

Ri-dichiarazione di Proprietà

Puoi re-dichiarare una proprietà in una sottoclasse, ma (ad eccezione di readonly contro readwrite) devi ripetere i suoi attributi per intero nelle sottoclassi. Vale lo stesso per una proprietà dichiarata in una categoria o in un protocollo - mentre le proprietà possono essere re-dichiarate in una categoria o protocollo, gli attributi di proprietà devono essere ripetute per intero.

Se dichiari una proprietà readonly in una classe, puoi re-dichiararla come readwrite in un estensione di classe , un protocollo o una sottoclasse. Nel caso di un'estensione re-dichiarazione di classe, il fatto che la proprietà era re-dichiarata prima di ogni statement @synthesize causerà la sintetizzazione del setter. L'abilità di re-dichiarare una proprietà readonly come read/write abilita due pattern di implementazioni comuni: una sottoclasse mutabile di una classe immutabile (NSstring, NSarray, e NSDictionary sono tutti esempi) e una proprietà che ha API pubbliche che è readonly ma ha un'implementazione readwrite privata interna alla classe. Il seguente esempio mostra l'uso di un estensione di classe per fornire una proprietà che è dichiarata come readonly nell'header pubblico ma che è re-dichiarata privatamente come read/write.
// public header file

@interface MyObject : NSObject {

NSString *language;

}

@property (readonly, copy) NSString *language;

@end



// private implementation file

@interface MyObject ()

@property (readwrite, copy) NSString *language;

@end



@implementation MyObject

@synthesize language;

@end


Copy

Se usi l'attributo di dichiarazione copy, specifichi che un valore è copiato durante l'assegnamento. Se sintetizzi l'accessorio corrispondente, il metodo sintetizzato usa il metodo copy. Questo è utile per attributi come oggetti String dove c'è una possibilità che il nuovo valore passato ad un setter potrebbe essere mutabile (ad esempio, un istanza di NSMutableString) e tu vuoi assicurarti che il tuo oggetto ha la sua copia privata immutabile. Ad esempio, se dichiari una proprietà come segue:
@property (nonatomic, copy) NSString *string;
allora il metodo setter sintetizzato è simile al seguente:
-(void)setString:(NSString *)newString {

if (string != newString) {

[string release];

string = [newString copy];

}

}

Sebbene questo funzioni bene per le stringhe, potrebbe presentare un problema se l'attributo è una collezione come un array o un insieme. Tipicamente vuoi che queste collezioni siano mutabili, ma il metodo copy restituisce una versione immutabile della collezione. In questa situazione devi fornire una tua propria implementazione del metodo setter, come illustrato nel seguente esempio:
@interface MyClass : NSObject {

NSMutableArray *myArray;

}

@property (nonatomic, copy) NSMutableArray *myArray;

@end



@implementation MyClass



@synthesize myArray;



- (void)setMyArray:(NSMutableArray *)newArray {

if (myArray != newArray) {

[myArray release];

myArray = [newArray mutableCopy];

}

}



@end

dealloc

Le proprietà dichiarate fondamentalmente prendono il posto delle dichiarazioni dei metodi accessori; quando sintetizzi una proprietà il compilatore crea soltanto qualsiasi metodo accessorio assente. Non c'è interazione diretta con il metodo dealloc - le proprietà non sono automaticamente rilasciate per te. Le proprietà dichiarate forniscono un utile metodo per controllare incrociatamente l'implementazione del metodo dealloc: Puoi cercare tutte le dichiarazioni di proprietà nel tuo file header e assicurarti che le proprietà dell'oggetto NON marcate con assign siano rilasciate, e quelle marcate con assign non siano rilasciate.

Nota: Tipicamente in un metodo dealloc dovresti rilasciare variabili di istanza di un oggetto direttamente (invece che invocare un metodo accessorio set passandogli nil come parametro), come illustrato in questo esempio:
- (void)dealloc {

[property release];

[super dealloc];

}

Se stai usando il runtime moderno e sintetizzando le variabili di istanza, non puoi accedere alle variabili di istanza diretatmente, quindi devi invocare il metodo accessorio:
- (void)dealloc {

[self setProperty:nil];

[super dealloc];

}

Core Foundation

Come già detto prima, prima di Mac OS X v10.6 non si poteva specificare l'attributo retain per tipi di non-oggetti. Se, quindi dichiari una proprietà il cui tipo è un CFType (Core Foundation Type) e sintetizzi i metodi accessori come illustrato nel seguente esempio:
@interface MyClass : NSObject

{

CGImageRef myImage;

}

@property(readwrite) CGImageRef myImage;

@end



@implementation MyClass

@synthesize myImage;

@end

allora in un ambiente reference counted il metodo set generato assegnerà semplicemente il nuovo valore alla variabile di istanza (il nuovo valore non è conservato e il vecchio valore non è rilasciato). Questo è tipicamente incorretto, quindi non dovreste sintetizzare i metodi ma dovreste implementarli voi.

In un ambiente garbage collected, se la variabile è dichiarata __strong:
...

__strong CGImageRef myImage;

...

@property CGImageRef myImage;

allora gli accessori sono sintetizzati appropriatamente - l'immagine non sarà CFRetain, ma il setter attiverà una barriera di scrittura.

Esempio

Il seguente esempio illustra l'uso di proprietà in molti modi diversi:
Il protocollo Link dichiara una proprietà, next.
  • MyClass adotta il protocollo Link quindi dichiara implicitamente anche la proprietà next. MyClass dichiara anche diverse altre proprietà.
  • creationTimestamp e next sono sintetizzate ma usano variabili di istanza esistenti con nomi diversi;
  • name è sintetizzato, e usa sintesi di variabile di istanza (richiamare quella sintesi della variabile di istanza non è supportato dal legacy runtime;
  • gratuitousFloat ha una direttiva dynamic — è supportata usando l'implementazione diretta dei metodi;
  • nameAndAge non ha una direttiva dynamic, ma questo è il valore di default; è supportato usando un'implementazione diretta dei metodi (dato che è readonly, richiede solo un getter) con un nome specificato (nameAndAgeAsString).

Listato 5-4 Dichiarare proprietà per una classe
@protocol Link

@property id <Link> next;

@end





@interface MyClass : NSObject <Link>

{

NSTimeInterval intervalSinceReferenceDate;

CGFloat gratuitousFloat;

id <Link> nextLink;

}

@property(readonly) NSTimeInterval creationTimestamp;

@property(copy) NSString *name;

@property CGFloat gratuitousFloat;

@property(readonly, getter=nameAndAgeAsString) NSString *nameAndAge;



@end





@implementation MyClass



@synthesize creationTimestamp = intervalSinceReferenceDate, name;

// Synthesizing 'name' is an error in legacy runtimes;

// in modern runtimes, the instance variable is synthesized.



@synthesize next = nextLink;

// Uses instance variable "nextLink" for storage.



@dynamic gratuitousFloat;

// This directive is not strictly necessary.



- (CGFloat)gratuitousFloat {

return gratuitousFloat;

}

- (void)setGratuitousFloat:(CGFloat)aValue {

gratuitousFloat = aValue;

}





- (NSString *)nameAndAgeAsString {

return [NSString stringWithFormat:@"%@ (%fs)", [self name],

[NSDate timeIntervalSinceReferenceDate] - intervalSinceReferenceDate];

}





- (id)init {

if (self = [super init]) {

intervalSinceReferenceDate = [NSDate timeIntervalSinceReferenceDate];

}

return self;

}



- (void)dealloc {

[nextLink release];

[name release];

[super dealloc];

}



@end

Sottoclassi con Proprietà

Puoi sovrascrivere una proprietà readonly per renderla scrivibile. Ad esempio, potresti definire una classe MyInteger con una proprietà readonly, value:
@interface MyInteger : NSObject

{

NSInteger value;

}

@property(readonly) NSInteger value;

@end



@implementation MyInteger

@synthesize value;

@end

Potresti poi implementare una sottoclasse, MyMutableInteger, che ridefinisce la proprietà per renderla scrivibile:
@interface MyMutableInteger : MyInteger

@property(readwrite) NSInteger value;

@end



@implementation MyMutableInteger

@dynamic value;



- (void)setValue:(NSInteger)newX {

value = newX;

}

@end

Prestazioni e Threading

Se fornisci la tua implementazione dei metodi, il fatto che dichiari una proprietà non ha effetto sulla sua efficienza o sulla sicurezza del thread.

Se usi proprietà sintetizzate, le implementazioni di metodo generate dal compilatore dipendono dalla specifica che fornisci. Gli attributi di dichiarazione che influenzano le prestazioni ed il threading sono retain, assign, copy, e nonatomic. Le prime tre influenzano soltanto la parte dell'implementazione dell'assegnamento del metodo set, come illustrato sotto (l'implementazione potrebbe non essere esattamente come mostrato):
// assign

property = newValue;



// retain

if (property != newValue) {

[property release];

property = [newValue retain];

}



// copy

if (property != newValue) {

[property release];

property = [newValue copy];

}

L'effetto dell'attributo nonatomic dipende dall'ambiente. Di default, gli accessori sintetizzati sono atomici. In un ambiente reference counted, garantire comportamenti atomici richiede l'uso di una chiusura; inoltre un oggetto restituito è conservato e autorilasciato, come abbiamo già visto precedentemente. Se tali accessori sono invocati frequentemente, questo potrebbe avere un impatto significante sulle prestazioni. In un ambiente garbage collected, la maggior parte dei metodi sintetizzati sono atomici.

È importante capire che l'obbiettivo dell'implementazione atomica è fornire robusti metodi accessori - non garantisce correttezza del codice. Sebbene "atomic" significa che l'accesso alla proprietà è thread-safe, rendere atomiche tutte le proprietà della tua classe non vuol dire che la tua classe o piu generalmente il grafico del tuo oggetto sia "thread safe" - la sicurezza del thread non può essere espressa al livello di metodi accessori individuali.

Differenze di Runtime

In generale il comportamento delle proprietà è identico su tutti i runtimes. C'è una differenza chiave: il runtime moderno supporta sintesi di variabili di istanza e il legacy runtime no.

Per far funzionare @synthesize nel legacy runtime, devi fornire una variabile di istanza con lo stesso nome e di tipo compatibile con la proprietà o specificare un altra variabile di istanza esistente nello statement @synthesize. con il runtime moderno, non fornisci una variabile di istanza, il compilatore ne aggiunge una per te. Ad esempio, data la seguente dichiarazione e implementazione di classe:
@interface MyClass : NSObject {

float sameName;

float otherName;

}

@property float sameName;

@property float differentName;

@property float noDeclaredIvar;

@end



@implementation MyClass

@synthesize sameName;

@synthesize differentName=otherName;

@synthesize noDeclaredIvar;

@end

Il compilatore per il runtime legacy dovrebbe generare un errore a @synthesize noDeclaredIvar; considerando che il compilatore per il runtime moderno aggiungerebbe una variabile di istana per rappresentare noDeclaredIvar.


Fine Sesta Parte

Finisce qui il sesto articolo di questa Guida dedicato alle Proprietà Dichiarate delle classi del linguaggio di programmazione Objective-C. Spero col tempo di realizzare un'utile manuale disponibile a tutti. Il prossimo post di questa guida, tratterà le Categorie e le Estensioni. Segnalatemi eventuali errori, o commentate l'articolo se l'avete trovato utile, anche per incentivarmi a continuare a pubblicare le mie traduzioni. Iscrivetevi ai feed del blog per essere sempre aggiornati automaticamente ogni volta che sono disponibili nuovi contenuti. nella colonna a destra di questo blog potete trovare tutti i link agli articoli di questa guida.

Nessun commento:

Posta un commento

Related Posts with Thumbnails