Visualizzazione post con etichetta classi. Mostra tutti i post
Visualizzazione post con etichetta classi. Mostra tutti i post

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...
Related Posts with Thumbnails