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.


Nessun commento:

Posta un commento

Related Posts with Thumbnails