venerdì 16 aprile 2010

Guida Objective-C in Italiano - Allocare e Inizializzare Oggetti


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

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



Allocare e Inizializzare Oggetti

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



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

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

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

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

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

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

L'Oggetto Restituito

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

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

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

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

[anObject init];

[anObject someOtherMessage];

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

[anObject someOtherMessage];

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

if ( anObject )

[anObject someOtherMessage];

else

...

Implementare un Inizializzatore

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

Vincoli e Convenzioni

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

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

// Designated initializer for NSObject is init

if (self = [super init]) {

creationDate = [[NSDate alloc] init];

}

return self;

}

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

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

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



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

NSSize size = anImage.size;

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



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

// Designated initializer for NSView is initWithFrame:

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



image = [anImage retain];

}

return self;

}

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

Gestire Fallimenti di inizializzazione

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

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

if (self = [super init]) {

creationDate = [[NSDate alloc] init];

}

return self;

}

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



if (anImage == nil) {

[self release];

return nil;

}



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

NSSize size = anImage.size;

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



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

// Designated initializer for NSView is initWithFrame:

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



image = [anImage retain];

}

return self;

}

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



if (self = [super init]) {



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

options:NSUncachedRead error:errorPtr];



if (data == nil) {

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

[self release];

return nil;

}

// implementation continues...

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

Coordinare Classi

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

if ( self = [super init] ) {

name = [string copy];

}

return self;

}

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

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

Figura 3-1 Incorporare un Metodo Inizializzatore Ereditato



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

return [self initWithName:"default"];

}

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

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

L'Inizializzatore Designato

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

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

return [self initWithName:string fromFile:NULL];

}

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

Figura 3-3 Coprire l'inizializzatore designato

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

if ( self = [super initWithName:string] )

...

}

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

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

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

Figura 3-4 Initialization Chain


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

Combinare Allocazione e Inizializzazione

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

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

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

+ (id)arrayWithObject:(id)anObject;

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

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

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

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

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

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

static Soloist *instance = nil;



if ( instance == nil ) {

instance = [[self alloc] init];

}

return instance;

}

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

Fine quarta parte

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

1 commento:

  1. volevo ringraziarti per il lavoro che stai facendo qui, non è stato facile cercare una guida in italiano per le basi di objective-C. un motivo in piu' per poi approfondire l'argomento , grazie di nuovo.

    RispondiElimina

Related Posts with Thumbnails