mercoledì 14 aprile 2010

Guida Objective-C in Italiano - Creare Classi e Interfacce


Proseguendo il discorso introdotto con gli ultimi due articoli, Guida all'Objective-C, parte 1 e parte 2, nei quali abbiamo rispettivamente parlato delle basi del linguaggio Objective-C, e delle Classi di questo linguaggio, oggi parleremo della definizioni delle Classi, e impareremo a dichiararne l'Interfaccia, e implementarne il codice; in ultimo chiariremo la differenza tra messaggi inviati a self e a super. 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 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.






Definire una Classe

Molta programmazione OO consiste nello scrivere codice per nuovi oggetti, definendo nuove classi. In Objective-C, le classi sono definite in due parti:
  • Un'Interfaccia che dichiara le firme dei metodi e le variabili di istanza della classe e nomina la sua superclasse.
  • Un'Implementazione che di fatto definisce la classe (contiene il codice che implementa i suoi metodi).
Queste sono tipicamente divise in due files, a volte comunque una definizione di classe può dividersi tra molti files attraverso l'uso di una caratteristica chiamata "categoria". Le categorie possono dividere in parti una definizione di classe o estenderne una esistente.

Files Sorgenti

Sebbene il compilatore non lo richieda, l'interfaccia e l'implementazione sono solitamente separate in due files. L'interfaccia deve essere resa disponibile a chiunque usi la classe.

Un singolo file può dichiarare o implementare più di una classe. Tuttavia, è consueto avere un file interfaccia separato per ogni classe, se non anche un file di implementazione separato. Tenere le interfacce delle classi separate, riflette meglio il loro stato come entità indipendenti.

I files di interfaccia e implementazione tipicamente sono nominati dopo la classe. Il nome dell'implementazione ha estensione .m, che indica che contiene codice sorgente Objective-C. Il file interfaccia può essere assegnato a qualsiasi altra estensione. Poichè è incluso in un altro file sorgente, il nome del file interfaccia ha solitamente estensione .h, l'estensione tipica dei files header. Ad esempio, la classe Rectangle sarebbe dichiarata in Rectangle.h e definita in Rectangle.m.

Separare l'interfaccia di un'oggetto dalla sua implementazione si adatta bene con la progettazione dei programmi OO. Un'oggetto è un'entità auto-contenuta che può essere vista dall'esterno quasi come una scatola nera. Una volta che hai determinato come un oggetto interagisce con gli altri elementi nel tuo programma, è fatta, una volta dichiarata la sua interfaccia, puoi liberamente modificare la sua implementazione senza influire su qualunque altra parte dell'applicazione.



Interfaccia di Classe

La dichiarazione di un'interfaccia di una classe inizia con la direttiva del compilatore @interface e finisce con la direttiva @end. (Tutte le direttive Objective-C al compilatore iniziano con la "@").



@interface NomeClasse : NomeSuperClasse

{

Dichiarazioni di variabili di istanza

}

dichiarazione di metodi

@end

La prima linea della dichiarazione presenta il nome della nuova classe e lo collega alla sua superclasse. La superclasse definisce la posizione della nuova classe nella gerarchia ereditaria, come discusso nella seconda parte di questa guida, nella sezione "Ereditarietà". Se vengono omessi i due punti e il nome della superclasse, la nuova classe è dichiarata come una classe radice, una rivale della classe NSObject.

Seguendo la prima parte della dichiarazione della classe, le parentesi graffe racchiudono la dichiarazione delle variabili di istanza, le strutture dati che fanno parte di ogni istanza della classe. Ecco una lista parziale delle variabili di istanza che potrebbero essere dichiarate nella classe Rectangle:



float width;

float height;

BOOL filled;

NSColor *fillColor;

I metodi per la classe saranno dichiarati in seguito, dopo le parentesi graffe che includono le variabili di istanza, e prima della fine della dichiarazione della classe I nomi dei metodi che possono essere usati da oggetti di classe, i metodi di classe, sono preceduti da un segno +:



+ alloc;
I metodi che le istanze di una classe possono usare, i metodi di istanza, sono preceduti da un segno -:




- (void)display;

Anche se non è pratica comune, puoi definire un metodo di classe e un metodo di istanza con lo stesso nome. Un metodo può anche avere lo stesso nome di una variabile di istanza. Questo è più comune, specialmente per i metodi che restituiscono il valore della variabile. Ad esempio, la classe Circle ha un metodo radius che potrebbe abbinarsi alla variabile di istanza radius.

I tipi restituiti dai metodi sono dichiarati usando la sintassi standard C per il casting da un tipo ad un altro:




- (float)radius;

I tipi degli argomenti sono dichiarati allo stesso modo:




- (void)setRadius:(float)aRadius;

Se un tipo restituito, o un tipo di un argomento, non è esplicitamente dichiarato, è assunto essere un tipo di default per i metodi e messaggi, un id. Il metodo alloc illustrato prima restituisce id.
Quando ci sono più di un argomento, gli argomenti sono dichiarati nel nome del metodo dopo i due punti. Gli argomenti spezzano il nome nella dichiarazione, proprio come in un messaggio. ad esempio:




- (void)setWidth:(float)width height:(float)height;

I metodi che prendono un numero variabile di argomenti, si dichiarano usando la virgola e i puntini sospensivi, proprio come farebbe una funzione:




- makeGroup:group, ...;

Importare l'Interfaccia

Il file interfaccia deve essere incluso in ogni modulo sorgente che dipende dall'interfaccia della classe, che include ogni modulo che crea un'istanza della classe, invia un messaggio per invocare un metodo dichiarato per la classe, oppure menziona una variabile di istanza dichiarata nella classe. L'interfaccia è solitamente inclusa nella direttiva #import:




#import "Rectangle.h"

Questa direttiva è identica a #include, tranne per il fatto che si accerta che lo stesso file non sia mai incluso più di una volta. è quindi preferita ed usata al posto della #include negli esempi di codice della documentazione dell'Objective-C.

Per riflettere il fatto che una definizione di classe si basa sulle definizioni di classi ereditate, un file interfaccia inizia importando l'interfaccia della sua superclasse:




#import "ItsSuperclass.h"



@interface ClassName : ItsSuperclass

{

instance variable declarations

}

method declarations

@end

Questa convenzione vuol dire che ogni file interfaccia include, indirettamente il file interfaccia di tutte le classi ereditate. Quando un modulo sorgente importa un'interfaccia di classe, ottiene le interfacce per l'intera gerarchia ereditaria sulla quale è costruita la classe.




Riferirsi ad altre classi

Un file interfaccia dichiara una classe e, importando la sua superclasse, implicitamente contiene le dichiarazioni per tutte le classi ereditate, dalla classe NSObject, scendendo attraverso le sue sottoclassi. Se l'interfaccia menziona classi non in questa gerarchia, bisogna importarle esplicitamente o dichiararle con la direttiva @class:




@class Rectangle, Circle;

Questa direttiva informa semplicemente il compilatore che Rectangle e Circle sono nomi di classi. Non importano i loro file interfaccia.
Un file interfaccia menziona nomi di classi quando tipa staticamente le variabili di istanza, i valori restituiti, e gli argomenti. Per esempio, questa dichiarazione:




- (void)setPrimaryColor:(NSColor *)aColor;
menziona la classe NSColor.

Dato che dichiarazioni come queste usano semplicemente il nome della classe come un tipo e non dipendono da altri dettagli dell'interfaccia della classe (i suoi metodi e variabili di istanza), la direttiva @class dà al compilatore un anticipo sufficiente di cosa aspettarsi. Comunque, dove l'interfaccia di una classe è di fatto usata (istanze create, messaggi inviati), l'interfaccia deve essere importata. Tipicamente, un file interfaccia usa @class per dichiarare le classi, e il file di implementazione corrispondente importa le loro interfacce (dato che ha bisogno di creare istanze di quelle classi o inviargli messaggi).

La direttiva @class minimizza l'ammontare di codice visto dal compilatore e dal linker, ed è quindi il modo più semplice di fornire una dichiarazione anticipata di un nome di classe. Per farla semplice, evita potenziali problemi che potrebbero occorrere nell'importare files che importano altri files. Ad esempio, se una classe dichiara una variabile di istanza di un'altra classe tipata staticamente, e i loro due files interfaccia si importano l'un l'altro, nessuna classe potrebbe compilare correttamente.




Il ruolo delle Interfacce

Lo scopo dei file interfaccia è di dichiarare la nuova classe ad altri moduli sorgenti (e ad altri programmatori). Contiene tutte le informazioni di cui loro hanno bisogno per lavorare con la classe (i programmatori potrebbero anche apprezzare un po' di documentazione).

Il file interfaccia dice agli utenti come la classe è connessa nella gerarchia ereditaria e quali altre classi - ereditate o semplicemente con riferimenti ad esse nella classe - sono necessarie.

Il file interfaccia permette anche al compilatore di sapere quali variabili di istanza contiene un oggetto, e dice ai programmatori quali variabili ereditano le sottoclassi. Sebbene le variabili di istanza siano naturalmente viste più come un problema dell'implementazione di una classe piuttosto che della sua interfaccia, loro devono tuttavia essere dichiarate nel file interfaccia. Questo perchè il compilatore deve essere al corrente della struttura di un oggetto dove è usato, non solo dove è definito. Come programmatore, comunque, puoi generalmente ignorare le variabili di istanza delle classi che usi, tranne quando definisci una sottoclasse.

Finalmente, attraverso la sua lista di dichiarazione dei metodi, il file interfaccia permette agli altri moduli di sapere quali messaggi può inviare all'oggetto di classe e alle istanze della classe. Ogni metodo che può essere usato fuori la definizione della classe è dichiarato nel file interfaccia; i metodi che sono interni all'implementazione della classe possono essere omessi.




Implementazione delle Classi

La definizione di una classe è strutturata molto come la sua dichiarazione. Essa inizia con la direttiva @implementation e finisce con la direttiva @end:




@implementation ClassName : ItsSuperclass

{

instance variable declarations

}

method definitions

@end

Comunque, ogni file di implementazione deve importare la sua propria interfaccia. Ad esempio, Rectangle.m importa Rectangle.h. Poichè l'implementazione non necessita di ripetere nessuna delle dichiarazioni che importa, può omettere con sicurezza:



  • Il nome della superclasse
  • Le dichiarazioni di variabili di istanza
Questo semplifica l'implementazione e la rende principalmente dedicata alla definizione dei metodi:



#import "ClassName.h"


@implementation ClassName

method definitions

@end

I metodi per una classe sono definiti, come le funzioni C, tra una coppia di parentesi graffe. Prima delle parentesi graffe, sono dichiarate alla stessa maniera di come sono dichiarate nel file interfaccia, ma senza il punto e virgola. Per esempio:



+ (id)alloc

{

...

}



- (BOOL)isFilled

{

...

}



- (void)setFilled:(BOOL)flag

{

...

}

I metodi che prendono un numero variabile di argomenti li gestiscono proprio come farebbe una funzione:



#import



...



- getGroup:group, ...

{

va_list ap;

va_start(ap, group);

...

}

Riferirsi a Variabili di Istanza

Di default, la definizione di metodi di istanza ha tutte le variabili di istanza dell'oggetto nel suo scope. Ci si può riferire a loro semplicemente tramite il nome. Sebbene il compilatore crei l'equivalente di strutture C per memorizzare variabili di istanza, l'esatta natura della struttura è nascosta. Non hai bisogno di nessuno dei due operatori della struttura (. o ->) per riferirsi ai dati di un oggetto. Per esempio, la seguente definizione di metodi si riferisce alla variabile di istanza filled del ricevente:




- (void)setFilled:(BOOL)flag

{

filled = flag;

...

}

nè l'oggetto ricevente nè la sua variabile di istanza filled sono dichiarati come argomenti di questo metodo, e la variabile di istanza cade ancora nel suo scope. Questa semplificazione della sintassi è una scorciatoia significante nello scrivere codice Objective-C.

Quando la variabile di istanza appartiene ad un oggetto che non è il ricevente, il tipo dell'oggetto deve essere reso esplicito al compilatore attraverso la tipatura statica. Riferendosi alla variabile di istanza di un oggetto staticamente tipato, è usata la struttura operatore puntatore (->).

Supponete, ad esempio che la classe Sibiling dichiarasse un oggetto staticamente tipato, twin come variabile di istanza:




@interface Sibling : NSObject

{

Sibling *twin;

int gender;

struct features *appearance;

}

Finchè le variabili di istanza dell'oggetto staticamente tipato sono nello scope della classe (come sono qui perchè twin è tipato con la stessa classe), un metodo di Sibiling può settarle direttamente:




- makeIdenticalTwin

{

if ( !twin ) {

twin = [[Sibling alloc] init];

twin->gender = gender;

twin->appearance = appearance;

}

return twin;

}

Lo Scope delle Variabili di Istanza

Anche se sono dichiarate nell'interfaccia della classe, le variabili di istanza sono più materia del modo in cui una classe è implementata piuttosto che del modo in cui è usata. Un'interfaccia di un oggetto alloggia nei suoi metodi, non nella sua struttura dati interna.
Spesso c'è una corrispondenza uno a uno tra un metodo e una variabile di istanza, come nel seguente esempio:




- (BOOL)isFilled

{

return filled;

}

Ma non è la regola. Alcuni metodi potrebbero restituire informazioni non memorizzate in variabili di istanza, e alcune variabili di istanza potrebbero memorizzare informazioni che un oggetto non vuole rivelare.

Così come una classe è revisionata di tanto in tanto, la scelta di variabili di istanza potrebbe cambiare, anche se i metodi che dichiara restano gli stessi. Finchè i messaggi sono il veicolo per interagire con le istanze della classe, questi cambi non interesseranno realmente l'interfaccia.

Per rafforzare l'abilità di un oggetto di nascondere i suoi dati, il compilatore limita lo scope delle variabili di istanza - che sarebbe, limitare la loro visibilità all'interno del programma. Ma per fornire flessibilità, permette anche di impostare esplicitamente lo scope a tre livelli differenti. Ogni livello è contrassegnato da una direttiva del compilatore:

@private: La v.i. è accessibile solo dentro alle classi che la dichiarano.

@protected: La v.i. è accessibile dentro le classi che la dichiarano e dentro le classi che la ereditano.

@public: La v.i. è accessibile dovunque

@package: Usando il moderno runtime, una v.i. del pacchetto @package agisce come @public dentro l'immagine che implementa la classe, ma @private all'esterno. Questo è simile a private_extern per variabili e funzioni. Ogni codice fuori all' immagine dell'implementazione della classe che prova ad usare la v.i. avrà un link error. Questo è molto utile per le v.i. nel framework classes, dove @private potrebbe essere troppo restrittivo ma @protected o @public troppo permissivi.

Questo è illustrato in Figura 2-1.



Figura 2-1 Lo scope delle variabili di istanza

Una direttiva applica a tutte le variabili di istanza elencate dopo essa, fino alla direttiva successiva o alla fine della lista. Nel seguente esempio, le variabili age ed evaluation sono private, mentre name, job ed wage sono protected, e boss è publica.



@interface Worker : NSObject

{

char *name;

@private

int age;

char *evaluation;

@protected

id job;

float wage;

@public

id boss;

}

Di default, tutte le variabili di istanza non segnate (come name sopra) sono @protected.
Tutte le variabili che la classe dichiara, non importa come sono segnate, sono dentro lo scope della definizione della classe. Ad esempio, una classe dichiara una variabile di istanza job, come la classe Worker mostrata sopra, può riferirsi ad essa in una definizione di metodo:



- promoteTo:newPosition

{

id old = job;

job = newPosition;

return old;

}

Ovviamente, se una classe non potrebbe accedere alle sue proprie variabili di istanza, le variabili di istanza sarebbero inutili.

Normalmente, una classe ha accesso anche alle variabili di istanza che eredita. L'abilità di riferirsi a una variabile è solitamente ereditata insieme con la variabile. Ha senso per le classi avere le loro intere strutture dati dentro al loro scope, specialmente se pensi di una definizione di classe come una pura elaborazione delle classi da cui eredita. Il metodo promoteTo: illustrato precedentemente, potrebbe essere già ben definito in ogni classe che eredita la variabile di istanza job dalla classe Worker.

Ci sono motivi per cui vorresti restringere le classi ereditanti dall'accedere direttamente a una variabile di istanza:

Una volta che una sottoclasse accede a una variabile di istanza ereditata, la classe che dichiara la variabile è legata a quella parte della sua implementazione. In versioni successive, non si può eliminare la variabile o modificare il ruolo che gioca senza inavvertitamente rompere la sottoclasse.

Inoltre, se una sottoclasse accede a una variabile di istanza ed altera il suo valore, potrebbe inavvertitamente introdurre bugs nella classe che dichiara la variabile, specialmente se la variabile è coinvolta in dipendenze interne alla classe.

Per limitare lo scope di una variabile di istanza, alla sola classe che la dichiara, dovete segnarla come @private, in modo da renderle disponibili solo alle sottoclassi chiamando metodi accessori, se esistono.

All'altro estremo, segnare una variabile @public la rende generalmente disponibile, anche fuori dalla definizione di classe che eredita o dichiara la variabile. Normalmente, per ottenere informazioni memorizzate in una variabile di istanza, altri oggetti devono inviare un messaggio richiedendola. Comunque, una variabile pubblica può essere raggiunta dovunque come se sia un campo di una struttura C. Ad esempio:




Worker *ceo = [[Worker alloc] init];

ceo->boss = nil;

Nota che l'oggetto deve essere staticamente tipato.

Segnare variabili di istanza come @public batte l'abilità di un oggetto di nascondere i suoi dati. Va in senso contrario a un principio fondamentale della programmazione OO - l'incapsulamento dei dati dentro gli oggetti dove sono protetti da vista ed errori involontari. Variabili pubbliche dovrebbero quindi essere evitate tranne in casi straordinari.



Messaggi a self e a super

Objective-C fornisce due termini che possono essere usati in una definizione di metodo per riferirsi all'oggetto che esegue il metodo - self e super.

Supponiamo, ad esempio, che definite un metodo reposition che necessita di cambiare le coordinate di qualsiasi oggetto su cui agisce. Può invocare il metodo setOrigin::, per fare il cambio tutto ciò che deve fare è mandare un messaggio setOrigin:: allo stesso oggetto a cui il messaggio reposition stesso era stato inviato. Quando stai scrivendo il codice di reposition, ti puoi riferire all'oggetto con o self o super. Il metodo reposition potrebbe leggere o:




- reposition

{

...

[self setOrigin:someX :someY];

...

}

o:



- reposition

{

...

[super setOrigin:someX :someY];

...

}

Qui, self e super si riferiscono entrambi all'oggetto ricevente un messaggio reposition, qualunque oggetto che può sembrare esserlo. I due termini sono abbastanza diversi comunque. self è uno degli argomenti nascosti che la routine di messaggi invia ad ogni metodo; è una variabile locale che può essere usata liberamente dentro un'implementazione di metodo, proprio come possono essere i nomi delle variabili di istanza. super è un termine che sostituisce self solo come il ricevente in un espressione di messaggio. Come ricevente, i due termini differiscono principalmente in come influenzano il processo di messaggi:



  • self cerca l'implementazione del metodo nel modo usuale, iniziando nella dispatch table della classe dell'oggetto ricevente. Nell'esempio sopra, sarebbe iniziato con la classe dell'oggetto ricevente il messaggio reposition.
  • super inizia la ricerca dell'implementazione del metodo in un luogo diverso. Inizia nella superclasse della classe che definisce il metodo dove appare super. Nell'esempio sopra, sarebbe iniziato con la superclasse della classe dove reposition è definito.
Dovunque super riceve un messaggio, il compilatore sostituisce un'altra routine di messaggio per la funzione obj_msgSend. La routine sostituta guarda direttamente alla superclasse della classe che definisce - che sarebbe, alla superclasse della classe che invia il messaggio a super - piuttosto che la classe dell'oggetto ricevente il messaggio.



Un Esempio

La differenza tra self e super diventa chiara in una gerarchia di tre classi. Supponiamo ad esempio, che creiamo un oggetto appartenente ad una classe chiamata Low. La superclasse di Low è Mid; La superclasse di Mid è High. Tutte e tre le classi definiscono un metodo chiamato negotiate, che usano per una varietà di scopi. In più, Mid definisce un metodo ambizioso chiamato makeLastingPeace, che ha bisogno anche del metodo negotiate. Questo è illustrato in Figura 2-2:



Figura 2-2 High, Mid, Low

Ora inviamo un messaggio al nostro oggetto Low per eseguire il metodo makeLastingPeace, e makeLastingPeace, in cambio, invia un messaggio negotiate allo stesso oggetto Low. Se il codice chiama questo oggetto self,



- makeLastingPeace

{

[self negotiate];

...

}

la routine di messaggi trova la versione di negotiate definita in Low, la classe di self. Comunque, se il codice sorgente di Mid chiama quest'oggetto super, la routine di messaggi trova la versione di negotiate definita in Low, la classe di self. Comunque, se il codice sorgente di Mid chiama quest'oggetto super,



- makeLastingPeace

{

[super negotiate];

...

}

la routine di messaggi troverà la versione di negotiate definita in High. Questa ignora la classe dell'oggetto ricevente (Low) e salta alla superclasse di Mid, dato che Mid è dove makeLastingPeace è definito. Nessuno dei messaggi trova la versione del metodo negotiate di Mid.
Come illustra questo esempio, super fornisce un modo per bypassare un metodo che sovrascrive un altro metodo. Qui abilita makeLastingPeace ad evitare la versione negotiate di Mid che ridefinisce la versione originale di High.
Non essendo in grado di raggiungere la versione di negotiate di Mid, potrebbe sembrare come un difetto, ma, in circostanze, ha ragione di evitarlo:



  • L'autore della classe Low, intenzionalmente sovrascrisse la versione di negotiate di Mid così che le istanze della classe Low (e delle sue sottoclassi) avrebbero invocato la versione ridefinita del metodo invece. Il progettista di Low non voleva che gli oggetti di Low eseguissero il metodo ereditato.
  • Nell'inviare il messaggio super, l'autore del metodo makeLastingPeace intenzionalmente saltò la versione di negotiate di Mid (e sopra qualunque versione che potrebbe essere stata definita in classi come Low che ereditano da Mid) per eseguire la versione definita nella classe High. Il progettista di Mid voleva usare la versione High di negotiate e non altre.
La versione di negotiate di Mid potrebbe ancora essere usata ma ci vorrebbe un messaggio diretto all'istanza di Mid per farlo.



Usare super

I messaggi a super permettono alle implementazioni dei metodi di essere distribuite tra più di una classe. Puoi sovrascrivere un metodo esistente per modificare o aggiungerlo ad esso, e ancora incorporare il metodo originale nella modifica:



- negotiate

{

...

return [super negotiate];

}

Per alcune attività, ogni classe nella gerarchia ereditaria può implementare un metodo che esegue parte del lavoro e passa i messaggi a super per il resto. Il metodo init, che inizializza un'istanza nuovamente allocata, è progettato per lavorare in questo modo. Ogni metodo init ha la responsabilità per inizializzare la variabile di istanza definita nella sua classe. Ma prima di farlo, invia un messaggio init a super per avere le classi che eredita dall'inizializzazione delle loro variabili di istanza. Ogni versione di init segue questa procedura, quindi le classi inizializzano le loro variabili di istanza in ordine di eredità:



- (id)init

{

if (self = [super init]) {

...

}

}

I metodi inizializzatori hanno alcuni vincoli aggiuntivi, e saranno descritti in dettaglio nei prossimi articoli.

È anche possibile concentrare funzionalità core in un metodo definite in una superclasse, ed avere sottoclassi che incorporano il metodo attraverso messaggi a super. Ad esempio, ogni metodo di classe che crea un'istanza deve allocare memoria per il nuovo oggetto ed inizializzare la sua variabile isa per la struttura della classe. Questo è tipicamente lasciato ad alloc e allocWithZone: definiti nella classe NSObject. Se un'altra classe sovrascrive questi metodi (un raro caso), può ancora prendere le funzionalità base inviando un messaggio a super.



Ridefinire self

super è semplicemente un flag al compilatore che gli dice dove iniziare a cercare il metodo da eseguire; è usato solo come il ricevitore di un messaggio. Ma self è un nome di variabile che può essere usato in tanti modi, anche assegnato a un nuovo valore.

C'è la tendenza di fare questo solo nella definizione dei metodi di classe. I metodi di classe spesso non si preoccupano dell'oggetto di classe, ma delle istanze della classe. Per esempio, molti metodi di classe combinano l'allocazione e l'inizializzazione di un'istanza, spesso settando i valori delle variabili di istanza allo stesso tempo. In questi metodi, saremmo tentati di inviare messaggi alle istanze nuovamente allocate e chiamare l'istanza self, proprio come in un metodo di istanza. Ma sarebbe un errore. self e super si riferiscono entrambi all'oggetto ricevente - l'oggetto che ottiene il messaggio che gli dice di eseguire il metodo. Dentro un metodo di istanza, self si riferisce all'istanza; ma dentro un metodo di classe, self si riferisce all'oggetto di classe. Questo è un esempio di cosa non fare:




+ (Rectangle *)rectangleOfColor:(NSColor *) color

{

self = [[Rectangle alloc] init]; // BAD

[self setColor:color];

return [self autorelease];

}

Per evitare confusione, è solitamente meglio usare una variabile piuttosto che self per riferirsi ad un'istanza in un metodo di classe:




+ (id)rectangleOfColor:(NSColor *)color

{

id newInstance = [[Rectangle alloc] init]; // GOOD

[newInstance setColor:color];

return [newInstance autorelease];

}

Infatti, piuttosto che inviare il messaggio alloc alla classe in un metodo di classe, è spesso meglio inviare alloc a self. In questo modo, se la classe è subclassata, e il messaggio rectangleOfColor è ricevuto da una sottoclasse, l'istanza ritornata sarebbe dello stesso tipo della sottoclasse (per esempio, il metodo array di NSArray è ereditato da NSMutableArray).




+ (id)rectangleOfColor:(NSColor *)color

{

id newInstance = [[self alloc] init]; // EXCELLENT

[newInstance setColor:color];

return [newInstance autorelease];

}

Ulteriori informazioni sull'allocazione e l'inizializzazione di oggetti in seguito.




Fine terza parte

Finisce qui il terzo articolo dedicato alla definizione delle interfacce delle classi, e all'implementazione del loro codice sorgente 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'allocazione e l'inizializzazione degli oggetti. Segnalatemi eventuali errori, o commentate l'articolo se l'avete trovato utile, anche per incentivarmi a continuare a pubblicare le mie traduzioni.


Continua...

sabato 10 aprile 2010

Guida Objective-C in Italiano - Le Classi


Ecco il secondo capitolo della Guida all'Objective-C, questo post sarà dedicato alle Classi e prosegue i concetti introduttivi dell'Objective-C discussi nell'articolo precedente.
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 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.


Le Classi

Un programma OO è tipicamente costituito da una varietà di oggetti. Un programma basato sui frameworks Cocoa potrebbero usare oggetti NSMatrix, oggetti NSWindow, oggetti NSDictionary, oggetti NSFont, oggetti NSText, e molti altri. I programmi spesso usano più di un oggetto dello stesso tipo o classe - diversi oggetti NSArray o oggetti NSWindow, ad esempio.

In Objective-C, definisci oggetti definendo la loro classe. La definizione di classe è un prototipo per un tipo di oggetti; dichiara le variabili di istanza che diventano parte di ogni membro della classe, e definisce un insieme di metodi che tutti gli oggetti della classe possono usare.

Il compilatore crea solo un oggetto accessibile per ogni classe, un class object che conosce come costruire nuovi oggetti appartenenti alla classe. (Per questo motivo è tradizionalmente chiamato un "factory object"). La classe oggetto è la versione compilata della classe; gli oggetti che costruisce sono istanze della classe. Gli oggetti che svolgono il lavoro principale del tuo programma sono istanze create dall'oggetto classe a runtime.

Tutte le istanze di una classe hanno lo stesso insieme di metodi, e un insieme di variabili di istanza. Ogni oggetto ha le sue proprie variabili di istanza, ma i metodi sono condivisi.

Per convenzione, i nomi delle classi iniziano con lettera maiuscola (come Rectangle); i nomi delle istanze iniziano tipicamente con una lettera minuscola (come myRectangle).


Ereditarietà

Le definizioni di classi sono aggiuntive; ogni nuova classe che definisci è basata su un'altra classe dalla quale eredita metodi e variabili di istanza. La nuova classe semplicemente aggiunge o modifica ciò che eredita. Non necessita di duplicare il codice ereditato.

L'ereditarietà collega tutte le classi insieme in un albero gerarchico con una singola classe alla radice. Quando scrivi un codice che è basato sul Foundation framework, quella classe radice è tipicamente NSObject. Ogni classe (tranne la radice) ha una superclasse un passo più vicina alla radice, ed ogni classe (incluso la classe radice) può essere la superclasse di un qualsiasi numero di sottoclassi un passo più lontano dalla radice. la figura 1-1 illustra la gerarchia per alcune delle classi usate nel programma di disegno.


Figure 1-1 Alcune classi del programma di disegno



Questa figura mostra che la classe Square è una sottoclasse della classe Rectangle, che è una sottoclasse di Shape, Shape è una sottoclasse di Graphic, e Graphic è una sottoclasse di NSObject. l'ereditarietà è cumulativa. Quindi gli oggetti di tipo Square hanno i metodi e le variabili di istanza definite per le classi Rectangle, Shape, Graphic, e NSObject, oltre a quelle definite specificamente per Square. Questo per dire che un oggetto quadrato (Square), non è solo un quadrato, è anche un rettangolo, una forma, un grafico, e un NSObject.

Ogni classe tranne NSObject può quindi essere vista come una specializzazione o un'adattamento di un'altra classe. Ogni sottoclasse successiva modifica il totale di ciò che è ereditato cumulativamente. La classe Square definisce solo il minimo necessario per trasformare un Rettangolo in un Quadrato.
Quando definisci una classe, puoi collegarla alla gerarchia dichiarando la sua superclasse; ogni classe che crei deve essere la sottoclasse di un'altra classe (a meno che non stai definendo una nuova classe radice). C'è un'abbondanza di superclassi disponibile. Cocoa include la classe NSObject e molti frameworks che contengono definizioni per più di 250 classi aggiuntive. Alcune sono classi che puoi usare ingorporandole nel tuo programma. Altre potresti volerle adattare alle tue proprie esigenze definendo una sottoclasse.

Alcune classi dei framework definiscono quasi tutto ciò di cui puoi aver bisogno, ma lascia alcune specifiche da implementare in una sottoclasse. Puoi quindi creare oggetti molto sofisticati scrivendo poche linee di codice, e riutilizzando il lavoro effettuato dai programmatori del framework.


La classe NSObject

NSObject è una classe radice, e quindi non ha una superclaasse. Definisce il framework base per gli oggetti Objective-C e le interazioni tra gli oggetti. Trasmette alle classi e alle istanze delle classi che ereditano da essa l'abilità di comportarsi come oggetti e cooperare con il sistema di runtime.
Una classe che non ha bisogno di ereditare nessun comportamento speciale da un'altra classe dovrebbe essere fatta come sottoclasse della classe NSObject. Le istanze della classe devono avere almeno l'abilità di comportarsi come oggetti Objective-C a rutime. Ereditando quest'abilità dalla classe NSObject è più semplice e più affidabile che reinventare una nuova definizione di classe.

Nota: Implementare una nuova classe radice, è un'attività delicata e con molti azzardi nascosti. La classe deve duplicare molto di quello che NSObject fà, come allocare istanze, connetterle alla loro classe, e identificarle al sistema di runtime. Per questo motivo, dovreste usare generalmente la classe NSObject fornita con Cocoa come classe radice.


Ereditare variabili di istanza

Quando un oggetto classe crea una nuova istanza, il nuovo oggetto contiene non solo le variabili di istanza che erano definite per la sua classe, ma anche le variabili di istanza definite per la sua superclasse e per la superclasse della superclasse, e così via tornando indietro fino alla classe radice. Quindi, la variabile di istanza isa definita nella classe NSObject diventa parte di ogni oggetto. isa connette ogni oggetto alla sua classe.

La Figura 1-2 mostra alcune delle variabili di istanza che possono essere definite per una particolare implementazione di Rectangle, e da dove potrebbero venire. Nota che le variabili che fanno dell'oggetto un Rectangle sono aggiunte a quelle che lo rendono una forma (classe Shape), un grafico (classe Graphic) e così via.


Figure 1-2 Variabili di istanza di Rectangle

Una classe non deve dichiarare variabili di istanza. deve semplicemente definire nuovi metodi e contare sulle variabili di istanza che eredita, se ha bisogno di qualsiasi variabile di istanza affatto. Per esempio, Sqare potrebbe non dichiarare nessuna variabile di istanza propriamente sua.


Ereditare metodi

Un oggetto ha accesso non solo ai metodi definiti per la sua classe, ma anche a quelli della sua superclasse e per la superclasse della superclasse, e così via tornando indietro fino alla classe radice della gerarchia. Un oggetto Square può usare metodi definiti in Rectangle, Shape, Graphic e NSObject così come quelli definiti nella sua propria classe.

Ogni nuova classe che definisci in un programma può quindi fare uso di codice scritto per tutte le classi sopra di lei nella gerarchia. Questo tipo di eredità è il maggior vantaggio della programmazione OO. Quando usi uno dei frameworks OO forniti da Cocoa, i tuoi programmi possono avvantaggiarsi delle funzionalità codificate nelle classi del framework. Devi solo aggiungere il codice che personalizza le funzionalità standard della tua applicazione.

Anche gli oggetti classe ereditano dalla loro superclasse. Ma poichè non hanno variabili di istanza, ereditano soltanto i metodi.


Sovrascrivere un metodo con un altro

C'è un'utile eccezione all'ereditarietà: quando definisci una nuova classe, puoi implementare un nuovo metodo con lo stesso nome di un metodo definito in una delle classi più in alto nella gerarchia. Il nuovo metodo sovrascrive l'originale; le istanze della nuova classe eseguono il nuovo piuttosto che l'originale, e sottoclassi della nuova classe lo ereditano piuttosto di ereditare l'originale.Ad esempio, Graphic definisce un metodo dispolay che Rectangle sovrascrivere per definire la sua propria versione di dispolay. il metodo di Graphic è disponibile a tutti gli oggetti che ereditano dalla classe graphic, ma non agli oggetti Rectangle, che invece eseguono la loro propria versione di display.
Sebbene sovrascrivere un metodo blocchi la versione originale dall'essere ereditata, gli altri metodi definiti nella nuova classe possono saltare il metodo ridefinito e trovare l'originale.
Un metodo ridefinito può anche incorporare il metodo che sovrascrive. Quando lo fà, il nuovo metodo serve solo per rifinire o modificare il metodo che sovrascrive piuttosto che sostituirlo del tutto. Quando diverse classi nella gerarchia definiscono lo stesso metodo, ma ogni nuova versione incorpora la versione che sovrascrive, l'implementazione del metodo è effettivamente sparsa tra tutte le classi.
Sebbene una sottoclasse può sovrascrivere i metodi ereditati, non può sovrascrivere le variabili di istanza. Dato che un oggetto ha memoria allocata per ogni variabile che eredita, non puoi sovrascrivere una variabile dichiarandone una nuova con lo stesso nome. Se provi, il compilatore protesterà.


Classi Astratte

Alcune classi sono progettate solo o soprattutto in modo che le altre classi possano ereditare da loro. Queste classi Astratte, raggruppano metodi e variabili di istanza che possono essere usate da un numero di diverse sottoclassi in una definizione comune. La classe astratta è tipicamente incompleta da sola, ma contiene codice utile a ridurre il carico di sue sottoclassi. (Poichè le classi astratte devono avere sottoclassi per essere utili, sono a volte chiamate superclassi astratte).
Diversamente da altri linguaggi, Objective-C non ha sintassi per marcare una classe come astratta, non previene da creare un'istanza di una classe astratta.
La classe NSObject è l'esempio canonico di una classe astratta in Cocoa. Non usi mai istanze di NSObject in un'applicazione - non sarebbe buona a niente; sarebbe un oggetto generico senza l'abilità di fare niente in particolare. La classe NSView, d'altra parte, fornisce un esempio di istanze di una classe astratta che potreste occasionalmente usare direttamente.

Le classi astratte contengono spesso codice che aiuta a definire la struttura di un'applicazione. Quando crei sottoclassi di queste classi, istanze delle tue nuove classi si adattano senza sforzo alla struttura dell'applicazione e funzionano automaticamente con gli altri oggetti.


Tipi di Classe

Una definizione di classe è una specifica per un tipo di oggetto. La classe in effetti definisce un tipo di dato. Il tipo è basato non solo sulla struttura della classe (variabili di istanza), ma anche sul comportamento(metodi).
Un nome di classe può apparire nel codice sorgente ogni volta che uno specificatore di tipo è permesso in C - ad esempio, come un argomento dell'operatore sizeof:


int i = sizeof(Rectangle);

Static Typing

Puoi usare un nome classe al posto di id per designare un tipo di oggetto:


Rectangle *myRectangle;
Poichè questo modo di dichiarare un tipo di oggetto dà al compilatore informazioni sul tipo di oggetto di cui si tratta, è conosciuto come static typing. Proprio come id è in effetti un puntatore, gli oggetti sono staticamente tipati come puntatori a una classe. Gli oggetti sono sempre di tipo puntatore. Lo Static Typing rende il puntatore esplicito; id lo nasconde.
Static typing permette al compilatore di effettuare qualche controllo di tipo - ad esempio, di avvisare se un oggetto può ricevere un messaggio a cui sembra non essere ingrado di rispondere - e per allentare alcune restrizioni che applicano agli oggetti tipati genericamente id. In più, può chiarire le tue intenzioni agli altri che reggono il tuo codice sorgente. Comunque, questa tecnica non batte il dynamic binding o altera la determinazione dinamica di una classe del ricevente a runtime.
Un oggetto può essere tipato staticamente alla sua propria classe o a qualunque altra classe da cui eredita. Ad esempio, dato che l'ereditarietà fa di un Rettangolo un tipo di Grafico, un'istanza di un Rectangle potrebbe essere staticamente tipata alla classe Graphic:


Graphic *myRectangle;

Introspezione dei tipi

Le istanze possono rivelare i loro tipi a runtime. Il metodo isMemberOfClass: definito nella classe NSObject, controlla se il ricevente è un'istanza di una classe particolare:


if ( [anObject isMemberOfClass:someClass] )

...

Il metodo isKindOfClass:, definito anche nella classe NSObject, controlla più generalmente se il ricevente eredita da o è un membro di una classe particolare (se ha la classe nel suo cammino gerarchico):


if ( [anObject isKindOfClass:someClass] )

...

L'insieme di classi per le quali isKindOfClass: restituisce YES è lo stesso insieme al quale il ricevente può essere staticamente tipato.
L'Introspezione non è limitata a informazioni di tipo. Vedremo successivamente metodi che restituiscono oggetti classe, riportano se un oggetto risponde a un messaggio, e rivelano altre informazioni.


Class Objects

Una definizione di classe contiene vari tipi di informazione, molte delle quali riguardano le istanze della classe:


  • Il nome della classe e della sua superclasse
  • Un template che descrive un insieme di variabili di istanza
  • Le dichiarazioni dei nomi dei metodi e i loro tipi di ritorno e argomenti
  • Le implementazioni dei metodi
Questa informazione è compilata e registrata in strutture dati rese disponibili al sistema di runtime. il compilatore crea solo un grande oggetto, un oggetto classe, per rappresentare la classe. Il class object ha accesso a tutte le informazioni sulla classe, il che vuol dire alle principali informazioni su come sono le istanze della classe. Può produrre nuove istanze in accordo al piano presente nella definizione della classe. Sebbene un oggetto classe abbia il prototipo di un istanza di una classe, non è un'istanza di per sè. Non ha variabili di istanza proprie nè può eseguire metodi intesi specificatamente per la classe - i metodi classe sono l'opposto dei metodi istanza. Un oggetto classe eredita i metodi classe dalle classi sopra di lui nella gerarchia, proprio come le istanze ereditano i metodi istanza.
Nel codice sorgente, l'oggetto classe è rappresentato dal nome della classe. Nel seguente esempio, la classe Rectangle restituisce il numero di versione della classe usando un metodo ereditato da NSobject:


int versionNumber = [Rectangle version];

Comunque, il nome classe è supportato per la classe oggetto solo come un ricevente in un espressione di messaggi. Alltrove, avrete bisogno di chiedere a un'istanza o alla classe per restituire l'id di classe. Entrambi rispondono a un messaggio classe:


id aClass = [anObject class];

id rectClass = [Rectangle class];

Come mostrano questi esempi, gli oggetti classe possono, come gli altri oggetti essere tipati come id. Ma gli oggetti classe possono essere anche tipati più specificamente al tipo di dato Class:


Class aClass = [anObject class];

Class rectClass = [Rectangle class];

Tutti gli oggetti classe sono tipi Class. Usando questo seplice nome per una classe è equivalente ad usare il nome della classe per tipare staticamente un'istanza.
Gli oggetti classe sono quindi oggetti full-fledged che possono essere tipati dinamicamente, ricevere messaggi, ed ereditare metodi da altre classi. Sono speciali solo in quanto sono creati dal compilatore, mancano le strutture dati (variabili di istanza) tranne quelli costruiti nella definizione di classe, e sono gli agenti che producono istanze a runtime.
Nota: Il compilatore costruisce anche un oggetto metaclasse per ogni classe. Esso descrive l'oggetto classe proprio come la classe descrive le istanze della classe. Ma mentre puoi mandare messaggi a istanze e al class object, l'oggetto metaclasse è usato solo internamente dal sistema di runtime.


Creare Istanze

Una funzione principale di un oggetto classe è di creare nuove istanze. Questo codice dice alla classe Rectangle di creare una nuova istanza di Rectangle ed assignarla alla variabile myRectangle:


id myRectangle;

myRectangle = [Rectangle alloc];

Il metodo alloc alloca diamicamente la memoria per le variabili della nuova istanza dell'oggetto e le inizializza tutte a 0 - tutte, tranne la variabile isa che connette la nuova istanza alla sua classe. A un oggetto per esere utile, generalmente serve essere inizializzato più completamente. Questa è la funzione del metodo init. L'inizializzazione tipicamente segue subito dopo l'allocazione:


myRectangle = [[Rectangle alloc] init];

Questa linea di codice, o una come questa, potrebbe essere necessaria prima che myRectangle possa ricevere qualunque dei messaggi illustrati negli esempi precedenti. Il metodo alloc restituisce una nuova istanza e l'istanza esegue un metodo init per settare il suo stato iniziale. Ogni oggetto classe ha almeno un metodo (come alloc) che lo abilita a produrre nuovi oggetti, ed ogni istanza ha almeno un metodo (come init) che la prepara all'uso. I metodi di inizializzazione prendono spesso argomenti per permettere di passare particolari valorie hanno parolechiave per etichettare gli argomenti (initWithPosition:size:, ad esempio, è un metodo che potrebbe inizializzare una nuova istanza di un Rectangle), ma inizia tutto con init.


Personalizzazione con Class Objects

Non è solo un capriccio del linguaggio Objective-C che le classi sono trattate come oggetti. è una scelta voluta, e a volte sorprendenti vantaggi per la progettazione. è possibile, ad esempio, personalizzare un oggetto con una classe, dove la classe appartiene a un insieme open-ended. Nell'Application Kit, ad esempio un oggetto NSMatrix può essere personalizzato in un particolare tipo di oggetto NSCell.
Un oggetto NSMatrix può prendersi la responsabilità di creare oggetti individuali che rappresentano le sue celle. Può farlo quando la matrice è prima inizializzata e dopo quando servono nuove celle. La matrice visibile che un oggetto NSMatrix disegna sullo schermo, può crescere e accorciarsi a runtime, forse in risposta alle azioni dell'utente. Quando cresce, la matrice necessita di poter produrre nuovi oggetti per riempire i nuovi slots che sono stati aggiunti.
Ma che tipo di oggetti dovrebbero essere? Ogni matrice mostra solo un tipo di NSCell, ma ce ne sono molti tipi diversi. La gerarchia ederitaria in figura 1-3 mostra alcune di quelle fornite dall'Application Kit. Tutte ereditano dalla classe generica NSCell:
Figure 1-3 Gerarchia ereditaria per NSCell

Quando una matrice crea oggetti NSCell, dovrebbero essere oggetti NSButtonCell per mostrare un insieme di bottoni o pulsanti, oggetti NSTextFieldCell per mostrare campi dove l'utente può inserire e modificare testo, o qualche altro tipo di NSCell? l'oggetto NSMatrix deve tenere conto di ogni tipo di cella, anche quelle non ancora inventate.
Una soluzione a questo problema è definire la classe NSMatrix come classe astratta e richiedendo ad ognuno che la vuole usare, di dichiararne una sottoclasse e implementare i metodi che producono nuove celle. Poiché stavano realizzando i metodi, gli utenti della classe dovrebbero accertarsi che gli oggetti che hanno creato siano del giusto tipo.
Ma questo richiede che altri svolgano il lavoro che dovrebbe essere fatto nella classe NSMatrix, e non necessariamente prolifera il numero di classi. Dato che un'applicazione può servire più di un tipo di NSMatrix, ognuna con un tipo diverso di NSCell, potrebbe diventare stipato con le sottoclassi di NSMatrix. Ogni volta che inventi un nuovo tipo di NSCell, dovrai anche definire un nuovo tipo di NSMatrix. Inoltre, programmatori su differenti progetti potrebbero virtualmente scrivere codice identico per fare la stessa cosa, tutto per compensare il fatto che NSMatrix non può farlo.
Una soluzione migliore, la soluzione che è attualmente adottata dalla classe NSMatrix, è di tenere conto che le istanze di NSMatrix siano inizializzate con un tipo di NSCell - con un oggetto classe. Definisce un metodo setCellClass: che passa l'oggetto classe per il genere di oggetto NSCell un NSMatrix dovrebbe usare per riempire spazi vuoti:


[myMatrix setCellClass:[NSButtonCell class]];
L'oggetto NSMatrix usa l'oggetto classe per produrre nuove celle, quando è inizializzato e ogni volta che è ridimensionato per contenere più celle. Questo tipo di personalizzazione sarebbe difficile se le classi non fossero oggetti che possono essere passati in messaggi e assegnati a variabili.


Variabili e Class Objects

Quando definisci una nuova classe, puoi specificare variabili di istanza. Ogni istanza della classe può mantenere la sua propria copia delle variabili che dichiari - ogni oggetto controlla i suoi propri dati. Comunque non ci sono "variabili di classe" come controparti alle variabili di istanza. Solo le strutture dati interne, inizializzate dalla definizione della classe, sono fornite per la classe. Inoltre, un class object non ha accesso alle variabili di istanza di qualsiasi istanza; non può inizializzarle, leggerle o modificarle.
Per tutte le istanze di una classe, per condividere i dati, bisogna definire una variabile esterna di un certo ordinamento. Il modo più sempice per farlo è dichiarare una variabile nel file di implementazione della classe come illustrato nel seguente frammento di codice:


int MCLSGlobalVariable;



@implementation MyClass

// implementation continues

In un'implementazione più sofisticata, puoi dichiarare una variabile statica, e fornire metodi di classe per gestirla. Dichiarare una variabile statica (static) limita il suo scope alla classe - e solo alla parte della classe che è implementata nel file. (Quindi a differenza delle variabili di istanza, le variabili statiche non possono essere ereditate da, o direttamente manipolate da sottoclassi). Questo pattern è comunemente usato per definire istanze di una classe condivise.


static MyClass *MCLSSharedInstance;



@implementation MyClass



+ (MyClass *)sharedInstance

{

// check for existence of shared instance

// create if necessary

return MCLSSharedInstance;

}

// implementation continues

Le variabili statiche aiutano a dare al class object più funzionalità; può avvicinarsi ad essere un oggetto più completo e versatile. Un class object può essere usato per coordinare le istanze che crea, dispensa istanze dalle liste di oggetti già creati, o gestisce altri processi essenziali all'applicazione. Nel caso che hai bisogno di un solo oggetto di una particolare classe, puoi mettere tutto lo stato dell'oggetto in una variabile statica e usare solo metodi classe. Questo salva i passi di allocare e inizializzare un'istanza.
Nota: è anche possibile usare variabili esterne che non sono dichiarate statiche, ma lo scope limitato di variabili statiche serve meglio allo scopo di incapsulamento dei dati in oggetti separati.


Inizializzare un class Object

Se vuoi usare un class object per qualunque cosa oltre allocare istanze, ti potrebbe servire inizializzarle come faresti con un istanza. Sebbene i programmi non allocano class objects, Objective-C fornisce un modo ai programmi per inizializzarli.
Se una classe fa uso di variabili statiche o globali, il metodi initialize è un buon posto per impostare i loro valori iniziali. Per esempio, se una classe mantiene un array di istanze, il metodo initialize potrebbe impostare l'array e anche allocare una o due istanze di default per averle pronte.
Il sistema di runtime invia un messaggio initialize ad ogni oggetto di classe prima che la classe riceva qualunque altro messaggio e dopo che la sua superclasse ha ricevuto il messaggio initialize. Questo da alla classe una possibilità di impostare ambiente di runtime prima che venga usata. Se non è richiesta nessuna inizializzazione, non avete bisogno di scrivere un metodo initialize per rispondere al messaggio.
Per l'ereditarietà, un messaggio initialize inviato ad una classe che non implementa il rispettivo metodo è inoltrato alla superclasse, anche se la superclasse ha già ricevuto il messaaggio initialize. Per esempio, assumiamo che la classe A implementi il metodo initialize, e la classe B eredita dalla classe A ma non implementa il metodo initialize. Appena prima che la classe B riceva il suo primo messaggio, il sistema di runtime gli invia initialize. Ma, poichè B non implementa initialize, al suo posto è eseguito il metodo initialize della classe A. Quindi, la classe A dovrebbe assicurare che la sua inizializzazione logica sia eseguita solo una volta, e per la classe appropriata. Per evitare l'inizializzazione logica pià di una volta usa il template nel listato seguente, quando implementa il metodo initialize.
Listing 1-3 Implementazione di un metodo initialize


+ (void)initialize

{

if (self == [ThisClass class]) {

// Perform initialization here.

...

}

}

Nota: Ricorda che il sistema di runtime invia initialize ad ogni classe individualmente. Quindi, in un'implementazione di classe del metodo initialize, non dovete inviare il messaggio initialize alla superclasse.


Metodi della Classe Radice

Tutti gli oggetti, classi e istanze, necessitano ugualmente un'interfaccia al sistema di runtime. Entrambi oggetti classe e istanze dovrebbero poter fare introspezione sulle loro abilità e riportare la loro posizione nell'ereditarietà gerarchica. è la provincia di NSObject a fornire quest'interfaccia.
Quindi questi metodi di NSObject non devono essere implementati due volte - una per fornire un'interfaccia di runtime per le istanze e ancora per duplicare quell'interfaccia per gli oggetti classe - ai class objects è data una dispensa speciale per eseguire metodi istanza definiti nella classe radice. Quando un class object riceve un messaggio, che non può rispondere con un metodo di classe, il sistema di runtime determina se c'è un metodo radice istanza che può rispondere. i soli metodi istanza che un class object può eseguire sono definiti nella classe radice, e solo se non c'è un metodo di classe che può fare il lavoro.


Nomi di classi nel codice sorgente

Nel codice sorgente, i nomi di classi possono essere usati solo in due contesti molto diversi. Questi contesti riflettono il ruolo duale di una classe come tipo di dati e come un oggetto:
Il nome classe può essere usato come il nome di un tipo per un genere di oggetto. Per esempio:


Rectangle *anObject;

Qui anObject è staticamente tipato come puntatore a un Rectangle. Il compilatore si aspetta che esso abbia la struttura dati di un'istanza di Rectangle e i metodi istanza definiti ed ereditati dalla classe Rectangle. Lo static typing abilita il compilatore ad eseguire migliori controlli di tipo e crea più codice sorgente e più auto documentazione. Solo le istanze possono essere staticamente tipate; gli oggetti classe non possono essere, dato che non sono membri di una classe, ma piuttosto appartenere al tipo di dato Class.
Come il ricevitore in un' espressione di un messaggio, i nomi di classi si riferiscono al class object. Quest'uso è stato illustrato in molti degli esempi precedenti. Il nome di classe può stare per l'oggetto classe solo come un messaggio ricevente.
Come il ricevente in un espressione di messaggi, il nome classe si riferisce al class object. Quest'uso è stato illustrato diverse volte negli esempi precedenti. I nomi di classe stanno per l'oggetto classe solo come un messaggio ricevente. In qualunque altro contesto, devi chiedere al class object di rivelare il suo id. L'esempio sotto passa la classe Rectangle come argomento di un messaggio isKindOfClass:.


if ( [anObject isKindOfClass:[Rectangle class]] )

...

Sarebbe stato illegale semplicemente usare il nome Rectangle come argomento. Il nome della classe può essere solo un ricevente. Se non sai il nome della classe al tempo di compilazione ma ce l'hai in una stringa a runtime, puoi usare NSClassFromString per restituire il class object::


NSString *className;

...

if ( [anObject isKindOfClass:NSClassFromString(className)] )

...

Questa funzione restituisce nil se la stringa passata non è un nome di classe valido. I nomi di classi esistono nello stesso namespace come le variabili globali e i nomi di funzioni. Una classe e una variabile globale non possono avere lo stesso nome. I nomi delle classi riguardano i soli nomi con visibilità globale.


Testare l'eguaglianza di Classi

Puoi testare due oggetti di classe per l'eguaglianza usando un confronto diretto. Ci sono molte caratteristiche nel framework Cocoa che dinamicamente e trasparentemente sottoclassano classi esistenti per estenderne le funzionalità. Quando succede, il metodo di classe è tipicamente sovrascritto, la sottoclasse dinamica è mascherata dalla classe rimpiazza. Quando testi per l'eguaglianza delle classi, puoi quindi confrontare i valori restituiti dal metodo classe piuttosto che quelli restituiti da un più basso livello di funzioni. Messo in termini di API:


[object class] != object_getClass(object) != *((Class*)object)
Puoi testare per l'eguaglianza due classi come segue:


if ([objectA class] == [objectB class]) { //...

Fine seconda parte

Finisce qui il secondo articolo dedicato alle basi del linguaggio di programmazione Objective C. Spero di realizzare un'utile manuale disponibile a tutti. Il prossimo post di questa guida, tratterà la definizione delle Classi, le Interfacce, l'implementazione di Classi e lo Scope delle variabili di istanza. Segnalatemi eventuali errori, o commentate l'articolo se l'avete trovato utile, in modo da incentivarmi a proseguire con le traduzioni.
Guida Obj-C parte 1

Continua...

giovedì 8 aprile 2010

Guida Objective-C in Italiano - Le Basi


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.

In questo primo articolo saranno descritti alcuni dei concetti base della programmazione Object Oriented di questo linguaggio, quali appunto gli oggetti, il dynamic typing, la gestione della memoria, i messaggi, il polimorfismo, il dynamic binding, e la sintassi punto. Nel prossimo articolo di questa serie, verranno trattate le Classi e i concetti legati ad esse.

Guida Objective-C

Come dice il nome, i programmi OO (Object Oriented) sono orientati agli oggetti. Un oggetto associa dei dati con delle particolari operazioni che possono usare o modificare questi dati. Obj-C fornisce un tipo di dati per identificare le variabili senza specificarne una particolare classe di oggetti, questo permette la scrittura dinamica. In un programma puoi tipicamente assicurare che disponi di oggetti che non servono più.

Object Basics

Le operazioni di un oggetto sono i metodi; i suoi dati sono le variabili di istanza. Un oggetto essenzialmente include una struttura dati e un gruppo di procedure.
Ad esempio, se stai scrivendo un programma di disegno che permette a un utente di creare immagini composte da linee, cerchi, rettangoli, testi, immagini mappate, ecc... potresti creare una classe per ogni tipo di forma di base che un utente può manipolare. Un oggetto Rettangolo che ha come variabili di istanza delle variabili che identificano la posizione del rettangolo nel disegno, insieme alla base e altezza. Altre variabili di istanza potrebbero definire il colore del rettangolo, se è pieno o vuoto, e un line pattern che può essere usato per mostrare il rettangolo. Una classe Rettangolo dovrebbe avere metodi per settare la posizione di un'istanza, la dimensione, il colore, il riempimento e line pattern insieme ad un metodo che fa mostrare l'istanza stessa (display).
In Objective-C, una variabile di istanza di un oggetto è interna all'oggetto e vi si può accedere solo tramite i metodi dell'oggetto.
Un oggetto vede solo i metodi progettati per lui, quindi non può per errore eseguire dei metodi di altri tipi di oggetti. Un oggetto incapsula nascondendo all'esterno sia le sue variabili di istanza, sia le implementazioni dei suoi metodi.

Id

In Objective-C, gli identificatori di oggetti sono un distinto data type: id.
Questo è il tipo generale per ogni razza di oggetto, senza tener conto della classe. id è definito come un puntatore a una struttura dati di un oggetto:

typedef struct objc_object {

Class isa;

} *id;
Tutti gli oggetti quindi hanno una variabile isa che gli dice di quale classe sono un'istanza.
Terminologia: Dato che il tipo Class è definito come un puntatore:


typedef struct objc_class *Class;
la variabile isa si riferisce spesso al “puntatore isa”.
Come una funzione C o un array, un oggetto è identificato dal suo indirizzo. Tutti gli oggetti, ad eccezione delle loro variabili di istanza o metodi, sono di tipo id.


id anObject;
Per i costrutti OO dell' Objective-C, come i valori di ritorno dei metodi, id sostituisce int come tipo di dati di default.
La parola chiave nil è definita come un oggetto nullo, un id con valore 0. id, nil, e gli altri tipi di base dell' Objective-C sono definiti nell'header file objc/objc.h.


Dynamic Typing

(Wiki: Un linguaggio di programmazione è dinamicamente tipato, quando la maggioranza dei suoi controlli dei tipi è eseguita a tempo di esecuzione, invece che a tempo di compilazione.)
Il tipo id è completamente non restrittivo. da solo non rende informazioni su un oggetto, eccetto che esso è un oggetto. Ma gli oggetti non sono tutti uguali. Un Rettangolo non avrà gli stessi metodi o variabili di istanza di un oggetto che rappresenta un'immagine mappata. A un certo punto, un programma necessita di più informazioni specifiche riguardo a ciò che l'oggetto contiene, quali sono le sue variabili di istanza, quali metodi può eseguire ecc... Dato che il tipo id non può fornire queste informazioni al compilatore, ogni oggetto deve poterle fornire a tempo di esecuzione.
La variabile di istanza isa identifica la classe dell'oggetto. Ogni oggetto Rettangolo deve poter dire al sistema di runtime che è un Rettangolo. Ogni Cerchio può dire che è un Cerchio. Oggetti con lo stesso comportamento (metodi) e gli stessi tipi di dati (variabili di istanza) sono membri della stessa classe.
Gli oggetti quindi sono dinamicamente tipati a runtime. Ogni volta che serve, il sistema di runtime può trovare l'esatta classe a cui un oggetto appartiene, semplicemente chiedendo all'oggetto. Dynamic typing in Objective-C serve come fondamento per il dynamic binding, di cui parleremo in seguito.
La variabile isa abilita anche gli oggetti ad eseguire introspection, per scoprire informazioni su se stessi (o altri oggetti). Il compilatore registra informazioni sulle definizioni di classi, in strutture dati per farle usare al sistema di runtime. Le funzioni del sistema di runtime usano isa, per scoprire queste informazioni a runtime. Usando il runtime system, puoi, ad esempio, determinare se un oggetto implementa o meno un particolare metodo, o scoprire il nome della sua superclasse.
È anche possibile dare al compilatore informazioni riguardo la classe di ogni oggetto tipandolo staticamente nel codice sorgente usando il nome di classe. le Classi sono dei particolari tipi di oggetti, e il nome classe può servire come un nome di tipo.


Gestione Memoria

In un programma Objective-C, è importante assicurare che gli oggetti siano deallocati quando non servono più, altrimenti l'impronta nella memoria dell'applicazione diventa più grande del necessario. è anche importante assicurare che non si deallochino oggetti che devono essere usati ancora.
Objective-C offre due ambienti per la gestione della memoria che permettono ai seguenti obbiettivi di incontrarsi:


  • Reference counting: dove sei ultimamente responsabile nel determinare il tempo di vita degli oggetti.
  • Garbage collection: dove passi la responsabilità nel determinare il tempo di vita degli oggetti a un collector automatico.
Garbage collection sarà descritta in seguito.


Object Messaging

Questa sezione spiega la sintassi dell'invio dei messaggi, incluso come nidificare espressioni dei messaggi.


Sintassi Messaggi

Per far fare qualcosa ad un oggetto, gli invii un messaggio dicendogli di applicare un metodo. In Objective-C, message expressions sono racchiusi tra parentesi quadre:


[receiver message]
Il receiver è un oggetto, e il messaggio dice cosa fare. nel codice sorgente, il messaggio è il nome del metodo e gli eventuali parametri che gli vengono passati. Quando un messaggio è inviato, il sistema di runtime seleziona il metodo appropriato dal repertorio del receiver e lo invoca.
Ad esempio: questo mesaggio dice all'oggetto myRectangle di eseguire il suo metodo display, che causa la visualizzazione del rettangolo:


[myRectangle display];
Il messaggio è seguito da un “;” come è normale per ogni linea di codice C. Il nome del metodo in un messaggio serve a "selezionare" un'implementazione di metodo. Per questo motivo, ci si riferisce spesso ai nomi dei metodi come dei selectors. I metodi possono anche prendere parametri, o argomenti Un messaggio con un singolo argomento è scritto con i due punti (:) dopo il nome del selettore, e con l'argomento a seguire a destra dei due punti. Questo costrutto è chiamato parolachiave; una keyword finisce con i duepunti, e un artomento che li segue, come mostrato in questo esempio:


[myRectangle setWidth:20.0];
Un nome di selettore include tutte le keywords, inclusi i due punti, ma non include nient'altro, come tipi di ritorno o tipi di parametri. Il messaggo immaginario di sotto dice all'oggetto myRectangle di impostare la sua origine alle coordinate (30.0, 50.0):


[myRectangle setOrigin:30.0 :50.0]; // This is a bad example of multiple arguments
Dato che i due punti sono parte del nome del metodo, il nome del metodo è chiamato setOrigin::. Ha due due punti, perchè prende due argomenti. Questo particolare metodo non si interfoglia con il nome del metodo con gli argomenti e, quindi, Il secondo argomento è effettivamente non etichettato ed è difficile determinare il tipo di scopo degli argomenti del metodo. Invece, nomi dei metodi dovrebbero interforgliare i nomi con gli argomenti in modo tale che il nome del metodo descriva naturalmente gli argomenti passati al metodo. Ad esempio, la classe Rettangolo potrebbe invece implementare un metodo setOriginX:y: che renda chiaro lo scopo dei due argomenti:


[myRectangle setOriginX: 30.0 y: 50.0]; // This is a good example of multiple arguments
Importante: Le sotto parti del nome del metodo non sono opzionali, nè il loro ordine può essere variato. Gli “Argomenti nominati” e i “keyword arguments”, spesso implicano che gli argomenti di un metodo possono variare a runtime, possono avere diversi valori di default, possono essere in ordine diverso, possono avere argomenti addizionali nominati. Questo Non è il caso dell'Objective-C. Una dichiarazione di metodo in Objective-C è semplicemente una funzione C che prepone due argomenti addizionali. Metodi che prendono un numero variabile di argomenti sono comunque possibili, anche se molto rari. Argomenti extra sono separati da virgole dopo la fine del nome del metodo. (Diversamente dai due punti, le virgole non sono considerati parte del nome.) Nel seguente esempio, al metodo immaginario makeGroup: è passato un argomento richiesto (group) e tre opzionali:


[receiver makeGroup:group, memberOne, memberTwo, memberThree];
Come le funzioni C standard, i metodi possono restituire valori. Il seguente esempio imposta le variabili isFilled a YES se myRectangle è disegnato come un rettangolo solido, o NO se è disegnato solo in forma outline.


BOOL isFilled;

isFilled = [myRectangle isFilled];
Nota che una variabile e un metodo possono avere lo stesso nome. Un espressione di messaggio può essere nidificata in un altra. Qui, il colore di un rettangolo è settato al colore di un altro:


[myRectangle setPrimaryColor:[otherRect primaryColor]];
Objective-C fornisce anche un operatore punto (.) che offre una sintassi compatta e conveniente per invocare un metodo accessorio di un oggetto.


Inviare messaggi a nil

In Objective-C, è valido inviare messaggi a nil— semplicemente non ha effetti a runtime. Ci sono diversi patterns in Cocoa che si avvantaggiano da questo fatto. Il valore restituito da un messaggio a nil può anche essere valido:
Se il metodo restituisce un oggetto, allora un messaggio inviato a nil restituisce 0 (nil), ad esempio:


Person *motherInLaw = [[aPerson spouse] mother];
Se aPerson’s spouse (sposo) è nil, allora mother è inviato a nil e il metodo restituisce nil.
Se il metodo restituisce un qualunque tipo puntatore, un qualunquenumero intero, di dimensioni inferiori o uguali a sizeof(void*), un float, un double, un long double, o un long long, allora un messaggio inviato a nil restituisce 0.
Se il metodo restituisce una struct, come definito da Mac OS X ABI Function Call Guide da essere ritornato in registri, allora un messaggio inviato a nil restituisce 0.0 per ogni campo nella struttura dati. Altri tipi di data struct non saranno riempiti con zero.
Se il metodo restituisce qualsiasi cosa tranne i tipi sopracitati, il valore restituito da un messaggio inviato a nil è indefinito. Il seguente frammento di codice illustra un uso valido dell'invio di un messaggio a nil:


id anObjectMaybeNil = nil;

// this is valid

if ([anObjectMaybeNil methodThatReturnsADouble] == 0.0)

{

// implementation continues...

}
Note: Il comportamento di inviare messaggi a nil è cambia leggermente con Mac OS X v10.5.
Su Mac OS X v10.4 e precedenti, un messaggio a nil è valido, fintanto che il messaggio restituisce un oggetto, qualsiasi tipo putatore, void, o qualsiasi intero di dimensioni inferiori o uguali a sizeof(void*); se lo fa, un messaggio inviato a nil restituisce nil. Se il messaggio inviato a nil restituisce qualunque cosa tranne i valori di tipi sopracitati, (ad esempio, se restituisce un tipo struct, un tipo in virgola mobile, o un vettore) il valore restituito è indefinito. Non dovresti affidarti ai valori restituiti da messaggi inviati a nil a meno che il tipo restituito dal metodo sia un oggetto, qualsiasi tipo puntatore, o intero inferiore o uguale a sizeof(void*).


Le variabili di istanza del ricevente

Un metodo ha accesso automatico alle variabili di istanza dell'oggetto receiver. Non avete bisogno di passarle al metodo come argomenti. Per esempio, il metodo primaryColor illustrato sopra non prende argomenti, ma può ancora trovare il colore primario per otherRect e restituirlo. Ogni metodo presuppone il receiver e le sue variabili di istanza, senza doverle dichiarare come argomenti.
Questa convenzione semplifica il codice sorgente Objective-C. Supporta inoltre il pensiero OO riguardo gli oggetti e i messaggi. Messaggi sono inviati ai receivers quanto le lettere sono consegnate a casa tua. Gli argomenti dei messaggi portano informazioni dall'esterno al receiver; non hanno bisogno di portare il receiver stesso.
Un metodo ha accesso automatico solo alle variabili di istanza del receiver. Se esso richiede informazioni riguardo a una variabile memorizzata in un altro oggett, deve inviare un messaggio all'oggetto chiedendogli di rivelare il contenuto della variabile. I metodi primaryColor e isFilled mostrati sopra sono usati per questo scopo.


Polimorfismo

Come gli esempi sopra illustrano, messaggi in Objective-C appaiono nella stessa posizione sintattica delle chiamate a funzioni standard in C. Ma, poichè i metodi "appartengono a" un oggetto, i messaggi si comportano differentemente rispetto alle chiamate di funzioni. In particolare, un oggetto può essere operato solo dai metodi che sono stati definiti per esso. Non può confonderli con messaggi definiti per altri tipi di oggetti, anche se un altro oggetto ha metodi con lo stesso nome. Questo significa che due oggetti possono rispondere diversamente allo stesso messaggio. Per esempio ogni tipo di oggetto invia un messaggio display per poter mostrare se stesso in un modo unico. Un cerchio e un rettanolo risponderebbero diversamente a istruzioni identiche per seguire il cursore.
Questa caratteristica, che si chiama polimorfismo, gioca un ruolo significante nella progettazione di programmi OO. Insieme con il dynamic binding permette di scrivere codice che potrebbe essere applicato a un qualsiasi numero di diversi tipi di oggetti, senza che tu debba scegliere ogni volta che scrivi il codice di che tipo di oggetti si tratta. Potrebbero anche essere oggetti che saranno sviluppati in seguito, da altri programmatori che lavoreranno su altri progetti. Se scrivi codice che invia un messaggio display a una variabile id, qualsiasi oggetto che ha un metodo display sarà un potenziale receiver.


Dynamic Binding

Una differenza cruciale tra chiamate di funzione e messaggi è che una funzione e i suoi argomenti sono uniti insieme nel codice compilato, ma un messaggio e un oggetto ricevente non sono uniti finché il programma non è in esecuzione ed il messaggio è inviato. Quindi, l'esatto metodo che è invocato per rispondere a un messaggio può essere determinato solo a runtime, non quando il codice è compilato.
Il metodo preciso che un messaggio invoca dipende dal ricevente (receiver). Differenti riceventi possono avere diverse implementazioni per lo stesso nome di metodo (polimorfismo). Il compilatore per trovare la giusta implementazione di metodo per un messaggio, dovrebbe sapere che tipo di oggetto è il ricevente - a che classe appartiene. Questa è un'informazione che il ricevente è abilitato a rivelare a runtime quando riceve un messaggio (dynamic typing), ma non è disponibile per le dichiarazioni dei tipi trovate nel codice sorgente.
La selezione di un'implementazione di metodo accade a runtime. Quando un messaggio è inviato, una routine di messaging di runtime guarda il ricevente e il metodo nominato nel messaggio. Individua l'implementazione del metodo corrispondente del ricevente, "chiama" il metodo, e gli passa un puntatore alle variabili di istanza del ricevente.
Questo dynamic binding (collegamento dinamico) dei metodi ai messaggi funziona con il polimorfismo per dare alla programmazione OO più flessibilità e potenza. Dato che ogni oggetto può avere la sua propria versione di un metodo, un programma può raggiungere una varietà di risultati, non variando il messaggio in se stesso, ma variando solo l'oggetto che riceve il messaggio. questo può essere fatto quando il programma è in esecuzione; i receivers possono essere decisi al volo e possono essere resi dipendenti da fattori esterni come le azioni dell'utente.
Quando si esegue codice basato su un Application Kit, ad esempio, utenti determinano che oggetti ricevono i messaggi dai comandi di menu come Taglia, Copia e Incolla. Il messaggio va a qualsiasi oggetto controlla la selezione attuale. Un oggetto che mostra testo può reagire a un messaggio copy diversamente da un oggetto che mostra immagini scannerizzate. Un oggetto che rappresenta un insieme di forme potrebbe rispondere diversamente da un Rettangolo. Dato che i messaggi non selezionano i metodi (i metodi non sono legati al messagio) fino al runtime, queste differenze sono isolate in metodi che rispondono al messaggio. Il codice che invia il messaggio non si deve interessare di loro; non deve neanche enumerare le possibilità. Ogni applicazione può inventare i suoi propri oggetti che rispondono nel loro proprio modo ai messaggi copy.
Objective-C porta il dynamic binding un passo avanti e permette anche al messaggio inviato (il metodo selettore) di essere una variabile che è determinata a runtime. Questo sarà discusso in seguito.


Dynamic Method Resolution

Puoi fornire implementazioni di classi e istanziare metodi a runtime usando il dynamic method resolution. Vedi Dynamic Method Resolution in the Objective-C Runtime Programming Guide per ulteriori dettagli


Sintassi Punto

Objective-C fornisce un operatore punto (.) che offre una sintassi compatta e conveniente che potete usare come alternativa alla notazione con parentesi quadre ([]s) per invocare metodi accessori. è particolarmente utile quando vuoi accedere o modificare una proprietà che è una proprietà di un altro oggetto.


Uso della Sintassi Punto

Anteprima:
Puoi usare la sintassi punto per invocare metodi accessori usando lo stesso pattern di accedere a elementi di strutture come illustrato nel seguente esempio:


myInstance.value = 10;

printf("myInstance value: %d", myInstance.value);
La sintassi punto è trasformata dal compilatore in invocazioni di metodi acessori (così non accedi direttamente a una variabile di istanza). Il codice di esempio scritto sopra è esattamente equivalente al seguente:


[myInstance setValue:10];

printf("myInstance value: %d", [myInstance value]);
Uso generale
Puoi leggere e scrivere proprietà usando l'operatore punto (.), come illustrato nel seguente esempio.
Listing 1-1 Accedere a proprietà usando la sintassi punto


Graphic *graphic = [[Graphic alloc] init];


NSColor *color = graphic.color;

CGFloat xLoc = graphic.xLoc;

BOOL hidden = graphic.hidden;

int textCharacterLength = graphic.text.length;



if (graphic.textHidden != YES) {

graphic.text = @"Hello";

}

graphic.bounds = NSMakeRect(10.0, 10.0, 20.0, 120.0);
(@"Hello"è una costante NSString object”)
Accedere a una proprietà chiama il metodo get associato alla proprietà, coì come settarla invoca il metodo set associato alla proprietà. A dispetto delle apparenze, la sintassi punto quindi preserva l'incapsulamento, infatti non accedi direttamente alle variabili di istanza. Il seguente listato è equivalente al codice mostrato sopra, ma usa la sintassi a parentesi quadre:
Listing 1-2 Accedere a proprietà usando la sintassi a parentesi quadre


Graphic *graphic = [[Graphic alloc] init];



NSColor *color = [graphic color];

CGFloat xLoc = [graphic xLoc];

BOOL hidden = [graphic hidden];

int textCharacterLength = [[graphic text] length];



if ([graphic isTextHidden] != YES) {

[graphic setText:@"Hello"];

}

[graphic setBounds:NSMakeRect(10.0, 10.0, 20.0, 120.0)];
Un vantaggio della sintassi punto è che il compilatore può segnalare un errore quando individua una write a una proprietà readonly, considerando che nel migliore dei casi può solo generare un metodo non dichiarato che avverte che hai invocato un metodo non-esistente setProperty: che fallirà a runtime.
Per le proprietà del tipo adatto del linguaggio C, il significato assegnazioni composte è ben definito. ad esempio, puoi aggiornare la proprietà lunghezza di un'istanza di NSMutableData usando assegnamenti composti:


NSMutableData *data = [NSMutableData dataWithLength:1024];

data.length += 1024;

data.length *= 2;

data.length /= 4;
che è equivalente a:


[data setLength:[data length] + 1024];

[data setLength:[data length] * 2];

[data setLength:[data length] / 4];
C'è un caso in cui le proprietà non possono essere usate. Considera il seguente frammento di codice:


id y;

x = y.z; // z is an undeclared property
Nota che y è nontipata e la proprietà z non è dichiarata. Ci sono diversi modi in cui questo può essere interpretato. Dato che è ambiguo, ilo statemente è trattato come un errore di proprietà non dichiarata. Se z è dichiarata, allora non è ambiguo se c'è solo una dichiarazione di una proprietà z, finchè avranno tutti lo stesso tipo (tipo BOOL) allora è legale. Una sorgente di ambiguità potrebbe anche sorgere da una proprietà dichiarata readonly.
Valori nil
Se un valore nil è incontrato durante l'attraversamento di proprietà, il risultato è lo stesso di inviare il messaggio a nil. Per esempio le seguenti coppie sono tutte equivalenti:


// each member of the path is an object

x = person.address.street.name;

x = [[[person address] street] name];



// the path contains a C struct

// will crash if window is nil or -contentView returns nil

y = window.contentView.bounds.origin.y;

y = [[window contentView] bounds].origin.y;



// an example of using a setter....

person.address.street.name = @"Oxford Road";

[[[person address] street] setName: @"Oxford Road"];
Self
Se vuoi accedere a proprietà di self usando metodi accessori, devi esplicitamente chiamare self come illustrato in questo esempio:


self.age = 10;
Se non vuoi usare self., Acedi alla variabile di istanza direttamente. nel seguente esempio, il metodo set per la proprietà age non è invocato:


age = 10;
Performance and Threading
La sintassi genera codice equivalente all'invocazione standard di un metodo. come risultato, il codice che usa la sintassi punto esegue esattamente la stessa cosa di un codice scritto direttamente usando metodi accessori. Dato che la sintassi punto semplicemente invoca i metodi, non sono introdotte ulteriori dipendenze del thread come risultato del suo uso.


Sommario d'uso

aVariable = anObject.aProperty;
Invoca il metodo aProperty e assegna il valore restituito a aVariable. Il tipo di proprietà aProperty e il tipo di aVariable devono essere compatibili, altrimenti avrai un warning nella compilazione.


anObject.name = @"New Name";
Invoca il metodo setName: su anObject, passando @"New Name" come argomento.
Ottieni un warning nella compilazione se setName: non esiste, se la proprietà name non esiste, o se setName: restituisce qualcosa ma void.


xOrigin = aView.bounds.origin.x;
Invoca il metodo bounds e definisce xOrigin a essere il valore dell'elemento della struttura di origin.x del NSRect restituito da bounds.


NSInteger i = 10;

anObject.integerProperty = anotherObject.floatProperty = ++i;
Assegna 11 a entrambi anObject.integerProperty e anotherObject.floatProperty. Il lato destro dell'assegnamento è pre-valutato e il risultato è passato a setIntegerProperty: e setFloatProperty:. Il risultato prevalutato è costretto come richiesto ad ogni punto dell'assegnamento.


Uso Errato

I seguenti patterns sono fortemente sconsigliati.


anObject.retain;
Genera un warning (warning: value returned from property not used.).


/* method declaration */

- (BOOL) setFooIfYouCan: (MyClass *)newFoo;



/* code fragment */

anObject.fooIfYouCan = myInstance;
Genera un compiler warning che setFooIfYouCan: non sembra apparire un metodo di set perchè non restituisce (void).


flag = aView.lockFocusIfCanDraw;
Invoca lockFocusIfCanDraw e assegna il valore restituito a flag. Non genera warning a menoche il tipo di flag non combacia con il tipo che il metodo ritorna.


/* property declaration */

@property(readonly) NSInteger readonlyProperty;

/* method declaration */

- (void) setReadonlyProperty: (NSInteger)newValue;



/* code fragment */

self.readonlyProperty = 5;
Dato che la proprietà è dichiarata readonly, questo codice genera un compiler warning (warning: assignment to readonly property 'readonlyProperty'). Perchè il metodo che setta è presente, funzionerà a runtime, ma semplicemente aggiungere un setter per una proprietà non implica readwrite.



Fine prima parte

Finisce qui questo primo articolo dedicato alle basi del linguaggio di programmazione Objective C. Spero di realizzare un'utile manuale disponibile a tutti. Segnalatemi eventuali errori, o commentate l'articolo se l'avete trovato utile, in modo da incentivarmi a proseguire con le traduzioni.

Guida Objective-C parte seconda: Le Classi  

Continua...
Related Posts with Thumbnails