venerdì 30 aprile 2010

Guida Objective-C in Italiano - Parte 8 - Riferimenti Associativi


Questa è l'ottava parte della Guida alla programmazione in Objective-C, oggi parleremo di Riferimenti Associativi.

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.




Riferimenti Associativi

Si usano i riferimenti associativi per simulare l'aggiunta di variabili di istanza di un oggetto ad una classe esistente.

I riferimenti associativi sono disponibili solo in Mac OS X v10.6 e successivi.

Aggiungere Memoria fuori a una Definizione di Classe

Usando i riferimenti associativi, puoi aggiungere memoria ad un oggetto senza modificare la dichiarazione di classe. Questo può essere utile se non hai accesso al codice sorgente della classe, o se per motivi di compatibilità binaria non puoi alterare il layout dell'oggetto.

Le Associazioni sono basate su una chiave, quindi per ogni oggetto puoi aggiungere quante associazioni vuoi, ognuna usando una chiave diversa. Un'associazione può anche assicurare che l'oggetto associato rimane valido per almeno il tempo di vita dell'oggetto sorgente (senza la possibilità di introdurre cicli non riscuotibili in un ambiente garbage-collected).

Creare Associazioni

Usate la funzione Objective-C di runtime objc_setAssociatedObject per creare un'associazione tra un oggetto ed un altro. La funzione prende quattro argomenti: l'oggetto sorgente, una chiave, il valore e una politica di associazione costante. Di queste, la chiave e la politica di associazione meritano ulteriori discussioni.
  • La chiave è un puntatore a void. La chiave per ogni associazione deve essere unica. Un pattern tipico è usare una variabile statica.
  • La politica specifica se l'oggetto associato è assegnato, conservato o copiato (assign, retain, copy) e se l'associazione è fatta atomicamente o non-atomicamente. Questo segue un pattern simile agli attributi di una proprietà dichiarata. Specifica la politica per la relazione usando una costante (vedi objc_AssociationPolicy).
Il seguente esempio mostra come puoi stabilire un'associazione tra un array ed una stringa.
Listato 7-1 Stabilire un'associazione tra un array e una stringa



static char overviewKey;

NSArray *array = [[NSArray alloc] initWithObjects:@"One", @"Two", @"Three", nil];

// For the purposes of illustration, use initWithFormat: to ensure the string can be deallocated

NSString *overview = [[NSString alloc] initWithFormat:@"%@", @"First three numbers"];

objc_setAssociatedObject(array, &overviewKey, overview, OBJC_ASSOCIATION_RETAIN);

[overview release];

// (1) overview valid

[array release];

// (2) overview invalid

Al punto (1), la stringa overview è ancora valida poiché la politica OBJC_ASSOCIATION_RETAIN specifica che l'array conserva l'oggetto associato. Quando l'array è deallocato, (al punto 2), overview è rilasciata e in questo caso quindi, anche deallocata. Se provi, ad esempio, ad annotare il valore di overview, genererai un'eccezione di runtime.



Recuperare Oggetti Associati

Puoi recuperare un oggetto associato usando la funzione di runtime di Objective-C objc_getAssociatedObject. Continuando l'esempio mostrato nel listato 7-1, puoi recuperare overview dall'array usando la seguente linea di codice:
NSString *associatedObject = (NSString *)objc_getAssociatedObject(array, &overviewKey);

Rompere Associazioni

Per rompere un'associazione, si usa tipicamente objc_setAssociatedObject, passandogli nil come valore. Continuando l'esempio mostrato nel listato 7-1, potresti rompere l'associazione tra array e stringa overview usando la seguente linea di codice:
objc_setAssociatedObject(array, &overviewKey, nil, OBJC_ASSOCIATION_ASSIGN);
(Dato che l'oggetto associato è stato impostato a nil, la politica non è attualmente importante). Per rompere tutte le associazioni di un oggetto, puoi usare objc_removeAssociatedObjects. In generale, è sconsigliato usare questo dato che rompe tutte le associazioni per tutti i clienti. Usa questa funzione solo se hai bisogno di restaurare un oggetto a condizioni incontaminate.

Esempio Completo

Il seguente programma combina gli esempi di codice delle sezioni precedenti.
#import
#import

int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

static char overviewKey;

NSArray *array = [[NSArray alloc] initWithObjects:@"One", @"Two", @"Three", nil];
// For the purposes of illustration, use initWithFormat: to ensure we get a // deallocatable string
NSString *overview = [[NSString alloc] initWithFormat:@"%@", @"First three numbers"];

objc_setAssociatedObject(array, &overviewKey, overview, OBJC_ASSOCIATION_RETAIN);
[overview release];

NSString *associatedObject = (NSString *)objc_getAssociatedObject(array, &overviewKey);
NSLog(@"associatedObject: %@", associatedObject);

objc_setAssociatedObject(array, &overviewKey, nil, OBJC_ASSOCIATION_ASSIGN);
[array release];

[pool drain];
return 0;
}

Fine Ottava parte

Finisce qui l'ottavo articolo di questa Guida dedicato ai Riferimenti Associativi del linguaggio di programmazione Objective-C. Spero col tempo di realizzare un'utile manuale disponibile a tutti. Il prossimo post di questa guida, tratterà l'Enumerazione Veloce, un utile strumento che fornisce una sintassi concisa, efficiente e sicura, per enumerare i contenuti di una collezione. 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 barra laterale del blog potete trovare l'elenco di tutti gli articoli di questa guida.
Continua...

giovedì 29 aprile 2010

Guida Objective-C in Italiano - Parte 7 - Categorie ed Estensioni


Eccoci alla settima parte della Guida alla programmazione in Objective-C, oggi parleremo di Categorie ed Estensioni.

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.


Categorie ed Estensioni

Una categoria ti permette di aggiungere metodi ad una classe esitente - anche ad una di cui no hai il codice sorgente. Questa è una caratteristica molto potente che ti permette di estendere le funzionalità di una classe esistente senza crearne una sottoclasse. Usando le categorie, puoi anche dividere l'implementazione delle tue classi tra molti files. Le estensioni delle classi sono simili, ma permettono ad API aggiuntive richieste di essere dichiarate per una classe in locazioni diverse dal blocco @interface della classe primaria.

Aggiungere Metodi a Classi

Puoi aggiungere metodi ad una classe dichiarandoli in un file interfaccia sotto un nome di categoria e definendoli in un file implementazione sotto lo stesso nome. Il nome di categoria indica che i metodi sono delle aggiunte ad una classe dichiarata da qualche altra parte, non una nuova classe. non pouoi, comunque usare una categoria per aggiungere variabili di istanza ad una classe.

I metodi che la categoria aggiunge diventano parte del tipo della classe. Ad esempio i metodi aggiunti alla classe NSArray in una categoria sono tra i metodi che il compilatore si aspetta di trovare nel repertorio dei metodi di un'istanza della classe NSArray. I metodi aggiunti alla classe NSArray in una sottoclasse non sono inclusi nel tipo NSArray. (Questo importa solo per oggetti tipati staticamente dato che lo static typing è l'unico modo per il compilatore di conoscere la classe di un oggetto).

I metodi di Categoria possono fare qualunque cosa che possono fare i metodi definiti nella classe. A runtime non c'è differenza. I metodi che la categoria aggiunge alla classe sono ereditati da tutte le sottoclassi, proprio come gli altri metodi.

La dichiarazione di un'interfaccia di categoria appare molto come una dichiarazione di interfaccia di classe - tranne che il nome di categoria è elencato tra parentesi dopo il nome di classe e la superclasse non è menzionata. A meno che i suoi metodi non accedano ad alcuna variabile di istanza della classe, la categoria deve importare il file interfaccia della classe che estende:
#import "ClassName.h"



@interface ClassName ( CategoryName )

// method declarations

@end

L'implementazione, come al solito, importa la sua propria interfaccia. Una convenzione comune per i nomi è che il nome del file di base della categoria è il nome della classe che la categoria estende seguito da un "+" seguito dal nome della categoria. Un'implementazione di categoria (in un file nominato NomeClasse+NomeCategoria.m) potrebbe quindi apparire così:
#import "ClassName+CategoryName.h"



@implementation ClassName ( CategoryName )

// method definitions

@end

Nota che una categoria non può dichiarare variabil idi istanza aggiuntive per la classe; include solo metodi. Comnque, tutte le variabili di istanza entro lo scope della classe sono anche nello scope della categoria. questo include tutte le variabili di istanza dichiarate dalla classe, anche quelle dichiarate @private.

Non c'è limite al numero di categorie che puoi aggiungere ad una classe, ma ogni nome di categoria deve essere diverso, ed ognuno dovrebbe dichiarare e definire un insieme diverso di metodi.

Come Usare le Categorie

Ci sono tanti modi di usare le categorie:
  • Per estendere classi definite da altrei implementatori. Ad esempio, se puoi aggiungre metodi alle classi definite nel frameworks Cocoa. I metodi aggiunti sono ereditati dalle sottoclassi e sono indistinguibili a runtime dai metodi originali della classe.
  • Come alternativa ad una sottoclasse. Piuttosto che definire una sottoclasse per estendere una classe esistente, attraverso una categoria puoi aggiungeremetodi alla classe direttamente. Ad esempio, puoi aggiungere categorie a NSArray e altre classi Cocoa. Come nel caso di una sottoclasse, non hai bisogno del codice sorgente della classe che stai estendendo.
  • Per distribuire l'implementazione di una nuova classe in files sorgenti separati. Ad esempio, puoi raggruppare i metodi di una classe grande in diverse categorie e mettere ogni categoria in un file diverso. Quando sono usate in questo modo, le categorie possono beneficiare del processo di sviluppo in un vari modi - Esse:
    • Forniscono un semplice modo di raggruppare metodi correlati. Metodi simili definiti in classi diverse possono essere messi insieme nello stesso file sorgente.
    • Semplifica la gestione di una classe grande quando molti sviluppatori contribuiscono alla sua definizione.
    • Ti permette di raggiungere alcuni dei benefici della compilazione incrementale per una classe molto grande.
    • Può aiutare a migliorare la località delle referenze per metodi comunemente usati.
    • Ti Abilita a configurare una classe diversamente per applicazioni separate, senza dover mantenere diverse versioni dello stesso codice.

Sebbene il linguaggio ti permette di usare una categoria per sovrascrivere i metodi che la classe eredita, o anche i metodi dichiarati nell'interfaccia della classe, è fortemente sconsigliato l'uso di questa funzionalità. Una categoria non è una sostituta per una sottoclasse. Ci sono molte imperfezioni significanti:
  • Quando una categoria sovrascrive un metodo ereditato, il metodo nella categoria può, come è solito, invocare l'implementazione ereditata tramite un messaggio a super. Comunque, se una categoria sovrascrive un metodo che esiste già nella classe della categoria, non c'è modo per invocare l'implementazione originale.
  • Una categoria non può sovrascrivere con affidabilità i metodi dichiarati in un altra categoria della stessa classe. Questo problema è particolarmente significante dato che molte delle classi Cocoa sono implementate usando le categorie. Un metodo basato su framework che provi a sovrascrivere potrebbe essere stato implementato in una categoria, e quindi non è definito quale implementazione ha la precedenza.
  • La presena di alcuni metodi potrebbe causare cambi dei comportamenti tra tutti i frameworks. Ad esempio, se aggiungi un'implementazione di windowWillClose: a NSObject, questo causerà che tutte le finestre delegate risponderanno a quel metodo e potrebbero modificare il comportamento di tutte le istanze di NSWindow. Questo potrebbe causare cambiamenti misteriosi nel comportamento e potrebbe condurre a crashes.

Categorie della Classe Radice

Una categoria può aggiungere metodi ad ogni classe, incluso nella classe radice. I metodi aggiunti a NSObject diventano disponibili a tutte le classi che sono collegate al tuo codice. Mentre questo può essere utile a volte, può anche essere abbastanza pericoloso. Sebbene potrebbe sembrare che le modifiche che le categorie fanno siano ben capite e di impatto limitato, l'ereditarietà gli da un ampio scope. Potresti fare dei cambi non intenzionali a classi non viste; potresti non conoscere tutte le conseguenze di ciò che stai facendo. Inoltre, gli altri che non sono a conoscenza dei tuoi cambi non capiranno cosa stanno facendo.

In aggiunta, ci sono altre due considerazioni da tenere a mente quando si implementano metodi per la classe radice:
  • Messaggi a super sono invalidi (non c'è la superclasse).
  • Gli oggetti di classe possono eseguire metodi di istanza definiti nella classe radice.
Normalmente gli oggetti di classe possono eseguire solo metodi di classe. Ma i metodi di istanza definiti nella classe radice sono un caso speciale. Essi definiscono un'interfaccia al sistema di runtime che tutti gli oggetti ereditano. Gli oggetti di classe sono oggetti full-fledged e devono condividere la stessa interfaccia.

Questa caratteristica significa che hai bisogno di tener conto della possibilità che un metodo di istanza che definisci in una categoria della classe NSObject potrebbe essere eseguito non solo da istanze ma da anche da oggetti di classe. Ad esempio, nel corpo del metodo, self potrebbe significare un oggetto di classe così come un'istanza.

Estensioni

Le estensioni di classi sono come categorie "anonime", tranne che i metodi che dichiarano devono essere implementati nel blocco principale @implementation per la classe corrispondente.

È comune per una classe avere un'API pubblicamente dichiarata e poi avere API aggiuntiva dichiarata privatamente per solo uso della classe o del framework dentro il quale risiede la classe. Puoi dichiarare tale API in una categoria (o in più di una categoria) in un file header privato o in un file di implementazione come descritto sopra. Questo funziona, ma il compilatore non può verificare che tutti i metodi dichiarati siano implementati.

Ad esempio, il compilatore compilerà senza errori le seguenti dichiarazioni e implementazioni:
@interface MyObject : NSObject

{

NSNumber *number;

}

- (NSNumber *)number;

@end



@interface MyObject (Setter)

- (void)setNumber:(NSNumber *)newNumber;

@end





@implementation MyObject



- (NSNumber *)number {

return number;

}

@end

Nota che non c'è implementazione del metodo setNumber:. Se è invocato a runtime genererà un errore.

Le estensioni di classe ti permettono di dichiarare API aggiuntive richieste per una classe in locazioni diverse da dentro al blocco primario @interface della classe, come illustrato nel seguente esempio:
@interface MyObject : NSObject

{

NSNumber *number;

}

- (NSNumber *)number;

@end



@interface MyObject ()

- (void)setNumber:(NSNumber *)newNumber;

@end





@implementation MyObject



- (NSNumber *)number {

return number;

}

- (void)setNumber:(NSNumber *)newNumber {

number = newNumber;

}

@end

Nota che in questo caso:
  • Non è dato un nome nelle parentesi nel secondo blocco @interface;
  • L'implementazione del metodo setNumber: appare dentro al blocco @implementation princpiale della classe.
L'implementazione del metodo setNumber. deve apparire dentro al blocco @implementation princpale della classe (non poui implementarlo in una categoria). Se questo non è il caso, il compilatore emetterà un warning avvisando che non può trovare una definizione di metodo per setNumber:.

Fine Settima Parte

Finisce qui il settimo articolo di questa Guida dedicato alle Categorie e Estensioni del linguaggio di programmazione Objective-C. Spero col tempo di realizzare un'utile manuale disponibile a tutti. Il prossimo post di questa guida, tratterà i Riferimenti Associativi, un utile strumento per simulare l'aggiunta di variabili di istanza di un oggetto ad una classe esistente. 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 barra laterale del blog potete trovare l'elenco di tutti gli articoli di questa guida.
Continua...

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.

Continua...

lunedì 19 aprile 2010

Guida Objective-C in Italiano - Parte 5 - I Protocolli


Siamo arrivati alla quinta parte della Guida all'Objective-C, negli articoli precedenti abbiamo parlato a lungo dei concetti base del linguaggio quali, gli Oggetti, le Classi, la definizione di Classi e Interfacce, e l'inizializzazione degli Oggetti. Oggi vi faremo luce su tutti gli aspetti di un utile strumento di questo linguaggio che è rappresentato dai Protocolli.

Ricordo che il presente 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. Questa guida potrebbe 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.

Protocolli

I protocolli dichiarano metodi che possono essere implementati in ogni classe. I protocolli sono utili in almeno tre situazioni:
  • Per dichiarare metodi che altri pensano di implementare
  • Per dichiarare l'interfaccia a un oggetto e nasconderla alla sua classe
  • Per catturare similarità tra le classi che non sono correlate gerarchicamente.

Dichiarare Interfacce da far implementare agli altri

Classe e categoria di interfacce dichiarano metodi che sono associati con una classe particolare - metodi principali che la classe implementa. Protocolli formali e informali, d'altro canto, dichiarano metodi che sono indipendenti da qualunque classe specifica, ma che qualunque classe, e forse molte classi, potrebbero implementare.

Un protocollo è semplicemente una lista di dichiarazioni di metodi, non allegate ad una definizione di classe. Per esempio, questi metodi che riportano azioni dell'utente sul mouse, potrebbero essere raggruppati in un protocollo:
- (void)mouseDown:(NSEvent *)theEvent;

- (void)mouseDragged:(NSEvent *)theEvent;

- (void)mouseUp:(NSEvent *)theEvent;

Ogni classe che vuole rispondere ad eventi del mouse dovrebbe adottare il protocollo e implementare i suoi metodi.

I protocolli liberano le dichiarazioni dei metodi dalla dipendenza dalla gerarchia di classe, così che possono essere usate in modi in cui classi e categorie non possono. I protocolli elencano metodi che sono (o possono essere) implementati da qualche parte, ma l'identità della classe che li implementa non è di interesse. Cosa è di interesse o meno è una particolare classe conforme al protocollo - se ha implementato dei metodi che il protocollo dichiara. Così gli oggetti possono essere raggruppati in tipi, non solo su basi di somiglianza dovute al fatto che ereditano dalla stessa classe, ma anche sulla base della loro somiglianza in conformazione allo stesso protocollo. Le classi in rami non collegati della gerarchia ereditaria, potrebbero essere tipate ugualmente perché sono conformi allo stesso protocollo.

I protocolli possono giocare un ruolo significante nella progettazione OO, specialmente quando un progetto è diviso tra più implementatori o se incorpora oggetti sviluppati in altri progetti. Il software Cocoa usa pesantemente i protocolli per supportare la comunicazione tra i processi attraverso i messaggi Objective-C.

Comunque, un programma Objective-C non ha bisogno di usare protocolli. A differenza della definizione di classi e le espressioni di messaggi, sono opzionali. Alcuni frameworks Cocoa li usano; altri no. Dipende tutto dal compito attuale.

Metodi per gli altri da implementare.

Se conosci la classe di un oggetto, puoi guardare la sua dichiarazione di interfaccia ( e le dichiarazioni delle interfacce delle classi da cui eredita) per trovare a quali messaggi risponde. Queste dichiarazioni annunciano i messaggi che ricevono. I protocolli forniscono anche un modo per fargli annunciare i messaggi che inviano.

La comunicazione funziona in entrambi i modi; oggetti inviano messaggi così come lì ricevono. Ad esempio, un oggetto potrebbe delegare responsabilità per una certa operazione ad un altro oggetto, o potrebbe in occasione semplicemente aver bisogno di chiedere informazioni ad un altro oggetto. In alcuni casi, un oggetto potrebbe voler notificare ad altri oggetti le sue azioni così che loro possano prendere qualsiasi misura collaterale che potrebbe essere richiesta.

Se sviluppi la classe di un sender e la classe di un receiver come parte dello stesso progetto (o se qualcun altro ti ha fornito il ricevitore e i suoi file interfaccia), questa comunicazione è facilmente coordinata. Il sender semplicemente importa il file interfaccia del receiver. Il file importato dichiara i selettori di metodo che il sender usa nei messaggi che invia.

Comunque, se sviluppi un oggetto che invia messaggi a oggetti che non sono definiti - oggetti che stai lasciando agli altri da implementare - non vuoi avere il file di interfaccia del ricevente. Vi serve un altro modo di dichiarare i metodi che usate nei messaggi ma che non implementate. Un protocollo serve a questo scopo. Informa il compilatore riguardo ai metodi che la classe usa informa anche gli altri implementatori dei metodi che hanno bisogno di definire per avere i loro oggetti che lavorano con i tuoi.

Supponiamo, ad esempio, che sviluppi un oggetto che chiede l'assistenza di un altro oggetto inviandogli il messaggio helpOut: ed altri messaggi. Fornisci una variabile di istanza assistant per memorizzare l'outlet per questi messaggi e definire un metodo compagno per impostare la variabile di istanza. Questo metodo permette agli altri oggetti di registrarsi come potenziali destinatari per i messaggi del tuo oggetto:
- setAssistant:anObject

{

assistant = anObject;

}

Allora, ogni volta che un messaggio sta per essere inviato ad assistant, è fatto un controllo per accertarsi che il ricevente implementi un metodo che possa rispondere:
- (BOOL)doWork

{

...

if ( [assistant respondsToSelector:@selector(helpOut:)] ) {

[assistant helpOut:self];

return YES;

}

return NO;

}

Dato che, quando hai scritto questo codice, non potevi sapere che tipo di oggetti avrebbero potuto registrarsi come assistant, puoi solo dichiarare un protocollo per il metodo helpOut:; non puoi importare il file interfaccia della classe che lo implementa.

Dichiarare Interfacce per Oggetti Anonimi

Un protocollo può essere usato per dichiarare i metodi di un oggetto anonimo, un oggetto di classe sconosciuta. Un oggetto anonimo può rappresentare un servizio o gestire un insieme limitato di funzioni, specialmente dove serve un solo grande oggetto della sua classe. (Oggetti che giocano un ruolo fondamentale nel definire l'architettura di un'applicazione e oggetti che devi inizializzare prima di usare non sono buoni candidati per l'anonimità).

Gli oggetti non sono anonimi al loro sviluppatore, ovviamente, ma sono anonimi quando lo sviluppatore fornisce loro a qualcun altro. Ad esempio, considera le seguenti situazioni:
  • Qualcuno che fornisce un framework o una suite di oggetti ad altri da usare può includere oggetti che non sono identificati da un nome di classe o un file interfaccia. Mancando nome e interfaccia di classe, gli utenti non hanno modo di creare istanze della classe. Invece, il fornitore deve fornire un'istanza pronta fatta. Tipicamente, un metodo in un'altra classe restituisce un oggetto usabile:

    id formatter = [receiver formattingService];

  • 
L'oggetto restituito dal metodo è un oggetto senza un'identità di classe, almeno non uno dei fornitori è disposto a rivelare. Affinchè sia utile, il fornitore deve voler identificare almeno alcuni dei messaggi a cui esso può rispondere. Questo è fatto associando l'oggetto con una lista di metodi dichiarati in un protocollo.
  • Puoi inviare messaggi Objective-C a oggetti remoti - oggetti in altre applicazioni. Ogni applicazione ha la sua propria struttura, classi e logica interna. Ma non ti serve sapere come un'altra applicazione funziona o con cosa i suoi componenti stanno comunicando. Da straniero, tutto quello che devi sapere è quali messaggi puoi inviare (il protocollo) e dove inviarli (il ricevente). Un'applicazione che pubblica uno dei suoi oggetti come potenziale ricevente di messaggi remoti, deve anche pubblicare un protocollo che dichiara i metodi che l'oggetto userà per rispondere a quei messaggi. Non deve rivelare nient'altro che l'oggetto. L'applicazione che invia non ha bisogno di conoscere la classe dell'oggetto o usare la classe nel suo proprio design Tutto ciò che gli serve è il protocollo.
I protocolli rendono possibili gli oggetti anonimi. Senza un protocollo, non ci sarebbe modo di dichiarare un'interfaccia a un oggetto senza identificare la sua classe.
Nota: anche se il fornitore di un oggetto anonimo non rivela la sua classe, l'oggetto stesso si rivela a runtime. Un messaggio di classe restituisce la classe dell'oggetto anonimo. Comunque, c'è solitamente poco punto nel cercare queste informazioni extra; L'informazione nel protocollo è sufficiente.

Somiglianze Non-Gerarchiche

Se più di una classe implementasse un insieme di metodi, quelle classi sarebbero spesso raggruppate sotto una classe astratta che dichiara i metodi che hanno in comune. Ogni sottoclasse potrebbe re-implementare i metodi a modo proprio, ma la gerarchia ereditaria e la dichiarazione in comune nella classe astratta cattura la somiglianza essenziale tra le sottoclassi.
Comunque, a volte non è possibile raggruppare metodi in comune in una classe astratta. Le classi non correlate in molti aspetti, che non potrebbero tuttavia aver bisogno di implementare alcuni metodi simili. Questa somiglianza singolare potrebbe non giustificare una relazione gerarchica. Ad esempio, potresti volere aggiungere un supporto per creare una rappresentazione XML degli oggetti nella tua applicazione e per inizializzare oggetti da una rappresentazione XML:
- (NSXMLElement *)XMLRepresentation;

- initFromXMLRepresentation:(NSXMLElement *)xmlString;

Questi metodi potrebbero essere raggruppati in un protocollo e le somiglianze tra le classi che lo implementano stimate per annotare che sono tutte conformi allo stesso protocollo.

Gli oggetti possono essere tipati a seconda delle somiglianze (i protocolli a cui sono conformi), piuttosto che a seconda della loro classe. Ad esempio, un'istanza di NSMatrix deve comunicare con gli oggetti che rappresentano le sue celle. La matrice potrebbe richiedere che ognuno di questi oggetti sia un tipo di NSCell (una classe basata su tipo) e affidarsi al fatto che tutti gli oggetti che eredita dalla classe NSCell hanno metodi che hanno bisogno di rispondere a messaggi di NSMatrix. Alternativamente, l'oggetto NSMatrix potrebbe richiedere che oggetti rappresentanti celle abbiano metodi che possono a rispondere a un particolare insieme di messaggi (un tipo basato su protocollo). In questo caso, l'oggetto NSMatrix non si preoccuperebbe di a quale classe appartiene un oggetto cella, appena ha implementato i metodi.

Protocolli Formali

Il linguaggio Objective-C fornisce un modo di dichiarare formalmente una lista di metodi come un protocollo. I protocolli formali sono supportati dal linguaggio e dal sistema di runtime. Ad esempio, il compilatore può controllare i tipi basati su protocolli, e gli oggetti possono introspezionare a runtime per riportare se sono o non sono conformi a un protocollo.

Dichiarare un Protocollo

I protocolli formali si dichiarano con la direttiva @protocol :
@protocol ProtocolName

method declarations

@end

Ad esempio, puoi dichiarare un protocollo di rappresentazione XML in questo modo:
@protocol MyXMLSupport

- initFromXMLRepresentation:(NSXMLElement *)XMLElement;

- (NSXMLElement *)XMLRepresentation;

@end

Diversamente dai nomi di classi, i nomi dei protocolli non hanno visibilità globale. Vivono nel loro proprio namespace.

Metodi di Protocolli Opzionali

I metodi dei protocolli possono essere segnati come opzionali usando la parola chiave @optional. La parola chiave @required, corrisponde a @optional e denota formalmente la semantica del comportamento di default. Puoi usare @optional e @required per partizionare il tuo protocollo in sezioni. Se non specifichi alcuna parola chiave, è richiesto @required.
@protocol MyProtocol



- (void)requiredMethod;



@optional

- (void)anOptionalMethod;

- (void)anotherOptionalMethod;



@required

- (void)anotherRequiredMethod;



@end

Nota: In Mac OS X v10.5, i protocolli potrebbero non includere le proprietà dichiarate opzionali. Questo vincolo è rimosso da Mac OS X v10.6 in poi.

Protocolli Informali

In aggiunta ai protocolli formali, puoi anche definire un protocollo informale raggruppando i metodi in dichiarazioni di categoria:
@interface NSObject ( MyXMLSupport )

- initFromXMLRepresentation:(NSXMLElement *)XMLElement;

- (NSXMLElement *)XMLRepresentation;

@end

I protocolli informali sono tipicamente dichiarati come categorie della classe NSObject, dato che associano largamente i nomi dei metodi con qualunque classe che eredita da NSObject. Dato che tutte le classi ereditano dalla classe radice, i metodi non sono ristretti ad alcuna parte della gerarchia. (Potrebbe anche essere possibile dichiarare un protocollo informale come categoria di un'altra classe per limitarlo a un certo ramo della gerarchia, ma ci sono poche ragioni per farlo).

Una volta usato per dichiarare un protocollo, un'interfaccia di categoria non ha un'implementazione corrispondente. Invece, le classi che implementano il protocollo dichiarano ancora i metodi nei loro propri files di interfaccia e li definiscono insieme con gli altri metodi nei loro files di implementazione.

Un protocollo informale piega le regole delle dichiarazioni di categoria per elencare un gruppo di metodi ma non associarli ad alcuna classe o implementazione particolare.

Per essere informali, i protocolli dichiarati nelle categorie non ricevono molto supporto dal linguaggio. Non ci sono controlli di tipo a tempo di compilazione e neanche un controllo a runtime per vedere se un oggetto è conforme al protocollo. Per ottenere questi benefici, dovete usare un protocollo formale. Un protocollo informale potrebbe essere utile quando tutti i metodi sono opzionali, come per un delegato, ma è tipicamente meglio usare un protocollo formale con i metodi opzionali.

Oggetti dei Protocolli

Proprio come le classi sono rappresentate a runtime dagli oggetti della classe e i metodi dai codici selettori, i protocolli formali sono rappresentati da una speciale istanza-tipo di dati della classe Protocol. Il codice sorgente che si occupa del protocollo deve riferirsi all'oggetto di Protocol.

In molti modi, i protocolli sono simili alle definizioni di classe. Entrambi dichiarano metodi, e a runtime entrambi sono rappresentati da oggetti - classi da oggetti Class e protocolli da oggetti Protocol. Come gli oggetti di classe, gli oggetti di protocollo sono creati automaticamente dalle definizioni e le dichiarazioni trovate nel codice sorgente e sono usati dal sistema di runtime. Non sono allocati e inizializzati in codice sorgente programma.

Il codice sorgente può riferirsi a un oggetto protocollo usando la direttiva @protocol( ) - la stessa direttiva che dichiara un protocollo, tranne che qui ha un insieme di parentesi tonde. Le parentesi racchiudono il nome del protocollo:
Protocol *myXMLSupportProtocol = @protocol(MyXMLSupport);
Questo è l'unico modo per il codice sorgente di evocare un oggetto Protocol. Diversamente da un nome di classe, un nome di protocollo non designa l'oggetto - tranne dentro @protocol( ).

Il compilatore crea un oggetto Protocol per ogni dichiarazione di protocollo che incontra, ma solo se il protocollo è anche:
  • Adottato da una classe, o
  • Riferito a qualche punto nel codice sorgente (usando @protocol( ) )
I protocolli che sono dichiarati ma non usati (tranne per il controllo dei tipi descritto sotto) non sono rappresentati da oggetti Protocol a runtime.

Adottare un Protocollo

Adottare un protocollo è simile in alcuni modi a dichiarare una superclasse. Entrambi i metodi definiscono alla classe. La dichiarazione di superclasse gli assegna i metodi ereditati; Il protocollo gli assegna metodi dichiarati nella lista del protocollo. Si dice che una classe adotta un protocollo formale se nella sua dichiarazione, elenca il protocollo tra parentesi angolari dopo il nome della superclasse:
@interface ClassName : ItsSuperclass < protocol list >
Le categorie adottano protocolli più o meno allo stesso modo:
@interface ClassName ( CategoryName ) < protocol list >
Una classe può adottare più di un protocollo; i nomi nella lista dei protocolli sono separati da virgole.
@interface Formatter : NSObject < Formatting, Prettifying >
Una classe o categoria che adotta un protocollo deve implementare tutti i metodi richiesti che il protocollo dichiara, altrimenti il compilatore emetterà un warning La classe Formatter sopra, definirebbe tutti i metodi richiesti dichiarati nei due protocolli che adotta, in aggiunta a qualunque potrebbe dichiarare lui stesso.

Una classe o categoria che adotta un protocollo deve importare il file header dove il protocollo è dichiarato. I metodi dichiarati nel protocollo adottato non sono dichiarati altrove nell'interfaccia di classe o categoria.

é possibile per una classe adottare semplicemente protocolli e dichiarare nessun altro metodo. Ad esempio, la seguente dichiarazione di classe adotta i protocolli Formatti e Prettyfying, ma di per se non dichiara variabili di istanza o metodi:
@interface Formatter : NSObject < Formatting, Prettifying >

@end

Conformarsi a un Protocollo

Si dice che una classe è conforme a un protocollo formale se adotta il protocollo o eredita da altre classi che lo adottano. Un'istanza della classe è detta conforme allo stesso insieme di protocolli a cui è conforme la sua classe.

Dato che una classe deve implementare tutti i metodi richiesti dichiarati nel protocollo che adotta, dicendo che una classe o un'istanza è conforme a un protocollo è equivalente a dire che ha nel suo repertorio tutti i metodi che il protocollo dichiara.

È possibile controllare se un oggetto è conforme ad un protocollo inviandogli il messaggio conformsToProtocol:
if ( ! [receiver conformsToProtocol:@protocol(MyXMLSupport)] ) {

// Object does not conform to MyXMLSupport protocol

// If you are expecting receiver to implement methods declared in the

// MyXMLSupport protocol, this is probably an error

}

(Nota che c'è anche un metodo di classe con lo stesso nome —conformsToProtocol: .)

Il test conformsToProtocol: è come il test respondsToSelector: per un singolo metodo, tranne che testa se un protocollo è stato adottato ( e presumibilmente tutti i metodi che dichiara sono implementati) piuttosto che solo se un particolare metodo è stato implementato. Poiché controlla tutti i metodi, conformsToProtocol: può essere più efficiente rispetto a respondsToSelector:.

Il test conformsToProtocol: è anche come il test isKindOfClass:, tranne per il fatto che testa su un tipo basato su un protocollo piuttosto che su un tipo basato su gerarchia ereditaria.

Controlli di Tipo

Le dichiarazioni di tipo per gli oggetti possono essere estese per includere protocolli formali. I protocolli così offrono la possibilità di un altro livello di controllo di tipi da parte del compilatore, uno che è più astratto dato che non è legato ad una particolare implementazione.
In una dichiarazione di tipo, i nomi dei protocolli sono elencati tra parentesi angolari dopo il nome del tipo:
- (id )formattingService;

id anObject;

Proprio come la tipatura satica permette al compilatore di testare su un tipo basato su gerarchia di classi, questa sintassi permette al compilatore di testare su un tipo basato sulla conformità ad un protocollo.

Ad esempio, se Formatter è una classe astratta, questa dichiarazione
Formatter *anObject;
raggruppa tutti gli oggetti che ereditano da Formatter in un tipo, e permette al compilatore di controllare gli assegnamenti contro quel tipo.

Analogamente, questa dichiarazione,
id anObject;
raggruppa tutti gli oggetti conformi al protocollo Formatting in un tipo, senza tener conto della loro posizione nella gerarchia delle classi. Il compilatore può accertarsi che solo gli oggetti conformi al protocollo siano assegnati al tipo.

In ogni caso, il tipo raggruppa oggetti simili - in ogni caso, perché condividono un'eredità comune, o perché convergono ad un comune insieme di metodi.

I due tipi possono essere combinati in una dichiarazione singola:
Formatter *anObject;
I protocolli non possono essere usati per tipare gli oggetti delle classi. Solo le istanze possono essere tipate staticamente ad un protocollo, proprio come ogni istanza può essere staticamente tipata ad una classe. (comunque, a runtime, sia le classi che le istanze risponderanno ai messaggi conformsToProtocol:).

Protocolli all'interno di Protocolli

Un protocollo può incorporare altri protocolli usando la stessa sintassi che le classi usano per adottare un protocollo:
@protocol ProtocolName < protocol list >
Tutti i protocolli elencati tra le parentesi angolari sono considerati parte del protocollo ProtocolName. Ad esempio se il protocollo Paging incorpora il protocollo Formatting,
@protocol Paging < Formatting >

qualsiasi oggetto conforme al protocollo Paging è conforme anche a Formatting. Dichiarazione di tipi
id someObject;
e messaggi conformsToProtocol:
if ( [anotherObject conformsToProtocol:@protocol(Paging)] )

...

hanno bisogno di menzionare solo il protocollo Paging per testare anche la conformità di Formatting.

Quando una classe adotta un protocollo, deve implementare i metodi richiesti che il protocollo dichiara, come abbiamo detto prima. In aggiunta, deve conformarsi a qualunque protocollo che il protocollo adottato incorpora. Se un protocollo incorporato incorpora ancora altri protocolli, la classe deve anche conformarsi a loro. Una classe può conformarsi ad un protocollo incorporato tramite o uno o l'altro di questi modi:
  • Implementando i metodi che il protocollo dichiara, o
  • ereditando da una classe che adotta il protocollo ed implementa i metodi.
Supponiamo, ad esempio, che la classe Pager adotti il protocollo Paging. Se Pager fosse una sottoclasse di NSObject,
@interface Pager : NSObject < Paging >
dovrebbe implementare tutti i metodi di Paging, inclusi quelli dichiarati nel protocollo incorporato Formatting. Adotta il protocollo Formatting insieme con Paging.

D'altro lato, se Pager è una sottoclasse di Formatter (una classe che indipendentemente adotta il protocollo Formatting),
@interface Pager : Formatter < Paging >
deve implementare adeguatamente tutti i metodi dichiarati nel protocollo Paging, ma non quelli dichiarati in Formatting. Pager eredita la conformità al protocollo Formatting da Formatter.

Nota che una classe può essere conforme a un protocollo senza adottarlo formalmente, semplicemente implementando i metodi dichiarati nel protocollo.

Referring to Other Protocols

Quando lavorando su applicazioni complesse, occasionalmente vi troverete a scrivere codice che assomiglia a questo:
#import "B.h"



@protocol A

- foo:(id <B>)anObject;

@end

dove il protocollo B è dichiarato così:
#import "A.h"



@protocol B

- bar:(id <A>)anObject;

@end

In una tale situazione, risulta la circolarità e nessun file compilerà correttamente. Per rompere questo ciclo ricorsivo, devi usare la direttiva @protocol per fare un riferimento anticipato al protocollo richiesto invece di importare il file interfaccia dove è definito il protocollo. Il seguente codice illustra come potresti farlo:
@protocol B;



@protocol A

- foo:(id <B>)anObject;

@end

Nota che usando la direttiva @protocol in questa maniera semplicemente si informa il compilatore che "B" è un protocollo che sarà definito successivamente. Non importa il file interfaccia dove è definito il protocollo B.

Fine quinta parte

E siamo alla fine del quinto articolo di questa Guida dedicato ai Protocolli del linguaggio di programmazione Objective-C. Spero col tempo di realizzare un'utile manuale disponibile a tutti. Il prossimo post di questa guida, tratterà la dichiarazione e l'implementazione di proprietà. 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.


Continua...

venerdì 16 aprile 2010

Guida Objective-C in Italiano - Allocare e Inizializzare Oggetti


Questo è il quarto articolo della serie Guida all'Objective-C, finora abbiamo introdotto i concetti base del linguaggio quali, gli Oggetti, le Classi, e la definizione di Classi e Interfacce. L'argomento trattato oggi è l'allocazione e l'inizializzazione degli Oggetti.

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. Questa guida potrebbe 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.



Allocare e Inizializzare Oggetti

Ci vogliono due passi per creare un oggetto usando Objective-C. Bisogna:
  • Dinamicamente allocare memoria per il nuovo oggetto
  • Inizializzare la nuova memoria allocata a valori appropriati
Un oggetto non è pienamente funzionale finché entrambi i passi sono stati completati. Ogni passo è compiuto da un metodo separato ma si può anche fare tutto in una singola linea di codice:
id anObject = [[Rectangle alloc] init];



Separare l'allocazione dall'inizializzazione vi da il controllo individuale su ogni passo in modo che ognuno può essere modificato indipendentemente dall'altro. Le sezioni seguenti badano prima all'allocazione e poi all'inizializzazione, e discutono come sono controllate e modificate.

In Objective-C, la memoria per nuovi oggetti è allocata usando metodi di classe definiti nella classe NSObject. NSObject definisce due metodi principali per questo scopo, alloc e allocWithZone:.

Questi metodi allocano abbastanza memoria per tenere tutte le variabili di istanza per un oggetto che appartiene alla classe ricevente. Non hanno bisogno di essere sovrascritti e modificati in sottoclassi.

I metodi alloc ed allocWithZone: inizializzano una variabile di istanza isa di un oggetto nuovamente allocato, in modo che punta alla classe dell'oggetto (l'oggetto della classe). Tutte le altre variabili di istanza sono impostate a 0 Di solito un oggetto ha bisogno di essere inizializzato più specificatamente prima di poter essere usato in modo sicuro.

Questa inizializzazione è la responsabilità di metodi di istanza specifici di classe che, per convenzione, iniziano con l'abbreviazione "init". Se il metodo non prende argomenti, il nome del metodo è giusto queste quattro lettere "init". Se prende argomenti, etichettare gli argomenti con il prefisso init. Ad esempio un oggetto NSView può essere inizializzato con un metodo initWithFrame:.

Ogni classe che dichiara variabili di istanza dovrebbe fornire un metodo init per inizializzarle. La classe NSObject dichiara la variabile isa e definisce un metodo init. Comunque, dato che isa è inizializzata quando è allocata memoria per un oggetto, tutto il metodo init di NSObject, fa e restituisce self NSObject dichiara il metodo principale per stabilire la convenzione per i nomi descritta prima.

L'Oggetto Restituito

Un metodo init normalmente inizializza le variabili di istanza del ricevente, poi le restituisce. é responsabilità del metodo restituire un oggetto che può essere usato senza errori.

Comunque, in alcune classi, questa responsabilità può restituire un oggetto diverso dal ricevente. Per esempio, se una classe mantiene una lista di oggetti nominati, potrebbe fornire un metodo initWitnName: per inizializzare le nuove istanze. Se ci possono essere non più di un oggetto per nome, initWithName: potrebbe rifiutarsi di assegnare lo stesso nome a due oggetti. Quando chiesto di assegnare a una nuova istana, un nome già usato da un altro oggetto, potrebbe liberare la nuova istanza allocata e restituire l'altro oggetto - così da assicurare l'unicità del nome mentre allo stesso tempo fornire cosa era stato chiesto, un'istanza con il nome richiesto.

In pochi casi, potrebbe essere impossibile per un metodo init fare cosa gli viene chiesto di fare. Ad esempio, un metodo initFromFile: potrebbe prendere i data che gli servono da un file passato come argomento. Se il nome del file passato non corrisponde ad u file attuale, non sarebbe in grado di completare l'inizializzazione. In casi come questo, il metodo init, potrebbe liberare il ricevente e restituire nil, indicando che l'oggetto richiesto non può essere creato.

Dato che un metodo init potrebbe restituire un oggetto diverso dal nuovo ricevente allocato, o anche restituire nil, è importante che i programmi usino il valore restituito dal metodo inizializzazione, non solo quelli ritornati da alloc o allocWithZone:. Il codice seguente è molto pericoloso, dato che ignora il valore di ritorno di init.
id anObject = [SomeClass alloc];

[anObject init];

[anObject someOtherMessage];

Invece, per inizializzare in modo sicuro un oggetto, dovreste combinare messaggi di allocazione e inizializzazione in una linea di codice.
id anObject = [[SomeClass alloc] init];

[anObject someOtherMessage];

Se c'è una possibilità che il metodo init possa restituire nil, allora dovresti controllare il valore restituito prima di procedere:
id anObject = [[SomeClass alloc] init];

if ( anObject )

[anObject someOtherMessage];

else

...

Implementare un Inizializzatore

Quando un nuovo oggetto è creato, tutti i bit di memoria (tranne per l'isa) - e quindi i valori per tutte le variabili di istanza - sono impostati a 0. In alcune situazioni, questo potrebbe essere tutto ciò che ti serve quando un oggetto è inizializzato; in molte altre, vuoi fornire altri valori di default per una variabile di istanza di un oggetto, o vuoi passare valori come argomenti all'inizializzatore. In questi altri casi, ti servirà scrivere un inizializer personalizzato. In Objective-C, gli inizializzatori personalizzati sono soggetti a più vincoli e convenzioni rispetto alla maggior parte degli altri metodi.

Vincoli e Convenzioni

Ci sono molti vincoli e convenzioni che si applicano ai metodi inizializzatori e che non si applicano agli altri metodi:
  • Da convenzione, il nome di un inizializzatore personalizzato inizia con init. Esempi dal framework Foundation includono, initWithFormat:, initWithObjects:, e initWithObjectAndKeys:.
  • Il tipo restituito di un metodo inizializzatore dovrebbe essere id. Il motivo di questo è che id da un'indicazione che la classe ha uno scopo non considerato espressamente - che la classe è nonspecificata e il soggetto cambia, a seconda del contesto dell'invocazione. Ad esempio, NSString fornisce un metodo initWithFormat:. Quando è inviato a un'istanza di NSMutableString (una sottoclasse di NSString), il messaggio restituisce un'istanza di NSMutableString, non di NSString.
  • Nell'implementazione di un inizializzatore personalizzato, dovete invocare per ultimo un inizializzatore designato. Gli inizializzatori designati saranno descritti in seguito. In breve, se stai implementando un nuovo inizializzatore designato, lui deve invocare l'inizializzatore designato della superclasse. Se stai implementando in qualunque altro inizializzatore, lui dovrebbe invocare l'inizializzatore designato della sua propria classe, o un altro del suo proprio inizializzatore che in ultimo invoca l'inizializzatore designato. Da default (come con NSObject), l'inizializzatore designato è init.
  • Dovresti assegnare self al valore restituito dall'inizializzatore. Questo perchè l'inizializzatore potrebbe restituire un oggetto diverso dal ricevente originale.
  • Se imposti il valore di una variabile di istanza, lo fai tipicamente usando un assegnamento diretto piuttosto che un metodo accessorio. Questo evita la possibilità di innescare effetti collaterati indesiderati negli accessori.
  • Alla fine dell'inizializzatore, dovete restituire self, ameno che l'inizializzatore fallisce, nel qual caso restituirete nil. Gli inizializzatori falliti saranno discussi più in dettaglio in seguito.
Il seguente esempio illustra l'implementazione di un inizializzatore personalizzato per una classe che eredita da NSobject e ha una variabile di istanza creationDate, che rappresenta il momento quando un oggetto è stato creato:
- (id)init {

// Assign self to value returned by super's designated initializer

// Designated initializer for NSObject is init

if (self = [super init]) {

creationDate = [[NSDate alloc] init];

}

return self;

}

(Il motivo di usare il pattern if (self = [super init]) sarà discusso in seguito).

Un inizializzatore non ha bisogno di fornire un argomento per ogni variabile. Per esempio, se una classe richiede che le sue istanze abbiano un nome e una sorgente dati, potrebbe fornire un metodo initWithName:fromURL:, ma impostare variabili di istanza non essenziali a valori arbitrari o permettere loro di avere valori nulli impostati di default. Potrebbe allora affidarsi a metodi come setEnabled:, setFriend:, e setDimensions: per modificare i valori di default dopo che la fase di inizializzazione è stata completata.

L'esempio succesivo illustra l'implementazione di un inizializzatore personalizzato che prende un singolo argomento. In questo caso, la classe eredita da NSView. Mostra che puoi svolgere del lavoro prima di invocare l'inizializzatore designato della superclasse.
- (id)initWithImage:(NSImage *)anImage {



// Find the size for the new instance from the image

NSSize size = anImage.size;

NSRect frame = NSMakeRect(0.0, 0.0, size.width, size.height);



// Assign self to value returned by super's designated initializer

// Designated initializer for NSView is initWithFrame:

if (self = [super initWithFrame:frame]) {



image = [anImage retain];

}

return self;

}

Questo esempio non mostra cosa fare se ci sono problemi durante l'inizializzazione.

Gestire Fallimenti di inizializzazione

In generale, se c'è un problema durante un'inizializzazione di un metodo, dovresti chiamare [self release] e restituire nil.

Ci sono due conseguenze principali per questa politica:
  • Ogni oggetto, (che sia la tua propria classe, una sottoclasse, o un chiamante esterno) che riceve un nil da un metodo inizializzatore dovrebbe essere in grado di occuparsene. Nel caso spiacevole dove il chiamante ha stabilito una qualsiasi referenza esterna all'oggetto prima della chiamata,questo include annullare qualsiasi connessione.
  • Ti devi accertare che i metodi dealloc siano sicuri in presenza di oggetti parzialmente inizializzati.
Nota: puoi chiamare solo [self release] al punto di fallimento. Se ottieni nil dall'invocazione dell'inizializzatore della superclasse, non dovresti chiamare anche release. Dovresti semplicemente pulire ogni referenza impostata che non si occupa di deallocare e restituire nil. Questo è tipicamente gestito dal pattern di eseguire inizializzazioni dentro un blocco dipendente su un testo dei valori di ritorno dell'inizializzatore della superclasse - come visto negli esempi precedenti:
- (id)init {

if (self = [super init]) {

creationDate = [[NSDate alloc] init];

}

return self;

}

Il seguente esempio costruisce ciò che abbiamo mostrato in Vincoli e Convenzioni per mostrare come gestire valori inappropriati passati al paramtetro:
- (id)initWithImage:(NSImage *)anImage {



if (anImage == nil) {

[self release];

return nil;

}



// Find the size for the new instance from the image

NSSize size = anImage.size;

NSRect frame = NSMakeRect(0.0, 0.0, size.width, size.height);



// Assign self to value returned by super's designated initializer

// Designated initializer for NSView is initWithFrame:

if (self = [super initWithFrame:frame]) {



image = [anImage retain];

}

return self;

}

L'esempio successivo illustra la miglior pratica dove, nel caso di un problema, c'è la possibilità di restituire informazioni significative nella forma di oggetto NSError restituito da riferimento:
- (id)initWithURL:(NSURL *)aURL error:(NSError **)errorPtr {



if (self = [super init]) {



NSData *data = [[NSData alloc] initWithContentsOfURL:aURL

options:NSUncachedRead error:errorPtr];



if (data == nil) {

// In this case the error object is created in the NSData initializer

[self release];

return nil;

}

// implementation continues...

Tipicamente non dovresti usare eccezioni per significare errori di questo tipo.

Coordinare Classi

I metodi init... che una classe definisce tipicamente inizializzano solo quelle variabili dichiarate in quella classe. Le variabili di istanza ereditate sono inizializzate inviando un messaggio a super per eseguire un metodo inizializzatore definito da qualche parte più su nella gerarchia ereditaria:
- (id)initWithName:(NSString *)string {

if ( self = [super init] ) {

name = [string copy];

}

return self;

}

Il messaggio a super collega insieme i metodi inizializzatori in tutte le classi ereditate. Dato che lui viene prima, assicura che le variabili della superclasse siano inizializzate prima di quelle dichiarate nelle sottoclassi. Per esempio, un oggetto Rectangle deve essere inizializzato come NSObnect, un Graphic, e una Shape prima di essere inizializzato come un Rectangle.

La connessione tra il metodo initWithName:illustrato sopra e il metodo ereditato init che incorpora è illustrata nella Figura 3-1:

Figura 3-1 Incorporare un Metodo Inizializzatore Ereditato



Una classe deve anche assicurarsi che tutti i metodi inizializzatori ereditati funzionino. Ad esempio, se la classe A definisce un metodo init e la sua sottoclasse B definisce un initWithName:, come mostrato in figura 3-1, B deve anche accertarsi che un messaggio init inizializza con successo le istanze di B. Il modo più facile per farlo è sostituire il metodo ereditato init con una versione che invochi initWithName:, come segue:
- init {

return [self initWithName:"default"];

}

Il metodo initWithName:, vorrebbe, in cambio, invocare il metodo ereditato come mostrato prima. La Figura 3-2 include la versione di B di init.

Figura 3-2 Coprire un modello di inizializzazione ereditato
Coprire un modello di inizializzazione ereditato, rende la classe che definisci più portabile alle altre applicazioni. Se lasci scoperto un metodo ereditato, qualcun altro potrebbe usarlo per produrre istanze per la tua classe inizializzate non correttamente.

L'Inizializzatore Designato

Nell'esempio dato nel paragrafo "Coordinare Classi", initWithName: sarebbe il metodo inizializzatore designato per la sua classe (classe B). L'inizializzatore designato è il metodo in ogni classe che garantisce che le variabili di istanza designate siano inizializzate (inviando un messaggio a super per eseguire un metodo ereditato). è anche il metodo che effettua la maggior parte del lavoro, e quello che altri metodi inizializzatori invocano nella stessa classe. è una convenzione di Cocoa che l'inizializzatore designato sia sempre il metodo che permette la maggior libertà di determinare il carattere di una nuova istanza (solitamente questo è quello con più argomenti, ma non sempre).

È importante conoscere l'inizializzaore designato quando si definisce una sottoclasse. Ad esempio, supponiamo di definire una classe C, una sottoclasse di B, ed implementare un metodo initWithName:fromFile:. In aggiunta a questo metodo, dobbiamo anche accertarci che i metodi ereditati init ed initWithName: funzionino per le istanze di C. Questo può essere fatto coprendo initWithName: di B con una versione che invoca initWithName:fromFile:.
- initWithName:(char *)string {

return [self initWithName:string fromFile:NULL];

}

Per un istanza della classe C, il metodo init ereditato invoca questa nuova versione di initWithName: che invoca initWithName:fromFile:. La relazione tra questi metodi è mostrata in figura 3-3:

Figura 3-3 Coprire l'inizializzatore designato

Questa figura omette un dettaglio importante. Il metodo initWithName:fromFile:, essendo l'inizializzatore designato per la classe C, invia un messaggio a super per invocare un metodo d'inizializzazione ereditato. Ma quale dei metodi di B dovrebbe invocare, init o initWithName:? Non può invocare init, per due ragioni:
  • Circolarità risulterebbe (init invoca initWithName: di C, che invoca initWithName:fromFile:, che invoca dinuovo init).
  • Non sarà ingrado di prendere vantaggio dall'inizializzazione nella versione initWithName: di B:.
Quindi, initWithName:fromFile: deve invocare initWithName::
- initWithName:(char *)string fromFile:(char *)pathname {

if ( self = [super initWithName:string] )

...

}

Principio Generale: l'inizializzatore designato in una classe deve, tramite un messaggio a super, invocare l'iizializzatore designato nella superclasse.

Gli inizializzatori designati sono concatenati l'un l'altro tramite messaggi a super, mentre altri metodi di inizializzazione sono concatenati a inizializzatori designati tramite il messaggio self.

Figura 3-4 mostra come tutti i metodi inizializzatori nelle classi A, B e C sono collegati. Messaggi a self sono mostrati sulla sinistra e i messaggi a super sulla destra.

Figura 3-4 Initialization Chain


Nota che la versione init di B invia un messaggio a self per invocare il metodo initWithName. Quindi, quando il ricevente è un'istanza della classe B, invoca la versione initWithName: di B, e quando il ricevente è un'istanza della clase C, invoca la versione di C.

Combinare Allocazione e Inizializzazione

In Cocoa, alcune classi definiscono metodi di creazione che combinano i due passi di allocare e inizializzare per restituire nuove istanze della classe inizializzate. Questi metodi sono spesso chiamati come costruttori convenienti e tipicamente prendono la forma + nomeClasse. Ad esempio, NSStrin ha i seguenti metodi (tra gli altri):
+ (id)stringWithCString:(const char *)cString encoding:(NSStringEncoding)enc;

+ (id)stringWithFormat:(NSString *)format, ...;

In modo analogo, NSArray definisce i seguenti metodi di classe che combinano allocazione e inizializzazione:
+ (id)array;

+ (id)arrayWithObject:(id)anObject;

+ (id)arrayWithObjects:(id)firstObj, ...;

Nota che il tipo restituito da questi metodi è id. Per lo stesso motivo dei metodi inizializzatori, come discusso precedentemente.

I metodi che combinano allocazione ed inizializzazione sono particolarmente di valore se l'allocazione deve in qualche modo essere informata dall'inizializzazione. Per esempio, se i dati per l'inizializzazione sono presi da un file, e il file potrebbe contenere abbastanza dati per inizializzare più di un oggetto, sarebbe impossibile conoscere quanti oggetti sono allcoati finchè il file è aperto. in questo caso, potresti implementare un metodo listFromFile: che prende il nome del file come un argomento. Aprirebbe ilfile, vede quanti oggetti da allocare, e crea una lista di oggetti larga abbastanza da tenere tutti i nuovi oggetti. Allora allocherebbe ed inizializzerebbe gli oggetti dai dati nel file, mettendoli nella lista, e finalmente restituendo la lista.

Ha senso anche combinare allocazione ed inizializzazione in un singolo metodo se vuoi evitare il passo di allocare alla cieca la memoria per un nuovo oggetto che potresti non usare. Come detto prima, un metodo init... potrebbe sostituire a volte un altro oggetto per il ricevente. Ad esempio, quando a initWithName: è passato un nome che è già preso, potrebbe liberare il ricevente e al suo posto restituire l'oggetto a cui li nome era stato precedentemente assegnato. Questo significa, che un oggetto è allocato e liberato immediatamente senza mai essere usato.

Se il codice che determina se il ricevente dovrebbe essere inizializzato è posto nel metodo che esegue l'allocazione invece che dentro init..., puoi evitare il passo di allocare una nuova istanza quando non è necessaria.

Nel seguente esempio, il metodo soloist assicura che non c'è più di un'istanza della classe Soloist. Esso alloca ed inizializza una singola istanza condivisa:
+ (Soloist *)soloist {

static Soloist *instance = nil;



if ( instance == nil ) {

instance = [[self alloc] init];

}

return instance;

}

Nota che in questo caso il tipo restituito è Soloist *. Dato che il metodo restituisce una singola istanza condivisa, la tipatura forte è appropriata - Non ci sono aspettative che questo metodo sarà sovrascritto.

Fine quarta parte

Finisce così anche il quarto articolo di questa Guida dedicato all'allocazione e inizializzzione di oggetti nel linguaggio di programmazione Objective-C. Spero col tempo di realizzare un'utile manuale disponibile a tutti. Il prossimo post di questa guida, tratterà l'uso di protocolli. Segnalatemi eventuali errori, o commentate l'articolo se l'avete trovato utile, anche per incentivarmi a continuare a pubblicare le mie traduzioni.

Continua...
Related Posts with Thumbnails