Objective-C: Le basi
Nei linguaggi di programmazione procedurali come il C, prima si pensa a quello che si vuole fare e soltanto dopo al "disegno" di quello che sara' il contenitore della nostra azione. Nella programmazione orientata agli oggetti succede esattamente il contrario.

Proviamo a pensare alla nostra automobile. E' un oggetto di cui siamo i proprietari. Quella che possediamo non e' un'automobile generica; essa contiene delle caratteristiche che la rendono unica: un paese di costruzione, un identificativo univoco (numero di telaio) e cosi' via.

Ogni linguaggio orientato agli oggetti ha una sua particolare terminologia. In Objective-C, la nostra automobile e' una istanza dell'oggetto automobile. Ancora, automobile e' il nome della classe dalla quale ha preso vita la nostra istanza; ogni volta che viene costruita una nuova automobile, viene creata una nuova istanza della classe automobile. Una singola istanza e' chiamata oggetto.

La nostra automobile potrebbe essere color argento metallizzato, avere interni neri, sedili in pelle e cosi' via. Inoltre, nella vita reale, eseguiremo delle azioni sulla nostra automobile: guidare, fare rifornimento, etc. Le caratteristiche e le azioni descritte possono essere effettuate con tutte le automobili. Parleremo, quindi, di metodi e variabili di istanza.


Istanze e metodi
Una occorrenza unica di una classe e' chiamata istanza. Le azioni che vengono effettuate su di essa sono chiamate metodi. A seconda del caso, un metodo puo' essere applicato ad una istanza della classe o alla classe stessa. Ad esempio, lavare l'automobile si applica ad una istanza della classe - in realta', tutti i metodi elencati nel precedente paragrafo possono essere inclusi nel concetto di metodi di istanza. Allo stesso modo, ottenere il numero di automobili che una particolare casa produttrice ha sul mercato potrebbe essere un esempio di metodo di classe - applicato, cioe', alla classe stessa e non ad una sua particolare istanza.
Supponiamo di essere abbastanza benestanti e di poterci permettere due automobili (una follia, considerato il costo del carburante e dell'assicurazione). Supponiamo, inoltre, che ognuna delle nostre due macchine sia identica all'altra: stesso colore, stessi interni etc. Entrambe le automobili vengono accese e ritirate nello stesso istante.
Dal momento in cui usciranno dal concessionario, le nostre due auto seguiranno percorsi di vita differenti e, sebbene inizialmente identiche, avranno una vita indipendente l'una dall'altra. Potremmo essere sfortunati e fare qualche graffio sulla prima automobile, mentre con la seconda potremmo camminare molto di piu' e consumare piu' velocemente le gomme. Potremmo dover fare rifornimento piu' spesso su una delle due automobili e dover lavare gli interni dell'altra ogni settimana.
Ognuna delle due automobili, insomma, avra' delle caratteristiche/esigenze proprie che non dipendono dal fatto di essere automobile ma dal fatto di essere un oggetto utilizzato. Queste caratteristiche cambiano dinamicamente con il passare del tempo: applicare un metodo ad un oggetto, significa alterarne alcune proprieta' o variabili di stato. Se il metodo usato e' quello di "fai il pieno", ad esempio, la variabile di stato che verra' alterata sara' sicuramente quella indicante il numero di litri di gasolio disponibili nel serbatoio.
Il concetto chiave, quindi, e' il fatto che ogni oggetto e' una rappresentazione unica di una classe. Ogni oggetto contiene delle informazioni (dati) che, tipicamente, vengono rappresentate come variabili private. I metodi di istanza forniscono lo strumento per accedere a queste variabili.

In Objective-C, la sintassi per indicare l'azione di "eseguire un metodo su di una istanza o classe di un oggetto" viene indicato con:

[ClassOrInstance method];



Ad una parentesi quadra aperta, viene fatto seguire il nome di una istanza di classe (o di una classe) con il nome del metodo interessato. Per concludere, viene specificata una parentesi quadra chiusa ed un punto e virgola.
Quando si chiede ad una classe (o istanza) di eseguire una particolare azione, diremo che stiamo inviando un messaggio. Il destinatario del nostro messaggio e' chiamato il ricevente. Potremmo formalizzare quanto detto cosi':

[receiver message];



Proviamo a riscrivere le azioni dell'oggetto automobile, proposte prima, sotto forma di metodi e messaggi. Prima di farlo, sara' necessario ottenere un riferimento alla nostra nuova automobile. Ricordiamo che i metodi a cui ci riferiamo sono metodi di istanza; applicabili, quindi, ad istanze dei nostri oggetti.

yourCar = [Car new]; //get a new car



Inviamo un messaggio di new alla classe Car, chiedendogli di volere indietro una nuova istanza dell'oggetto automobile. L'oggetto risultante - la nostra automobile - viene conservato in una variabile di riferimento: yourCar. Da questo momento in poi, yourCar sara' il riferimento alla nostra nuova istanza dell'oggetto.
Nel mondo reale, per comprare un'automobile, bisogna recarsi da un concessionario. Analogamente, in Objective-C per ottenere una nuova automobile bisogna chiamare un factory method o metodo di classe: new.



Il resto delle azioni sulla nostra nuova automobile saranno metodi di istanza; si applicheranno, quindi, alla nostra automobile e non alla classe delle automobile.

[yourCar prep]; //get it ready for firt-time use
[yourCar drive]; //drive your car
[yourCar wash]; //wash your car
[yourCar getGas]; //put gas in your car if you need it
[yourCar service]; //service your car
]yourCar topDown]; //if it's convertible
[yourCar topUp]; //if it's convertible



Sulla seconda automobile useremo gli stessi metodi, riferendoci alla seconda istanza dell'oggetto automobile:

[yourSecondCar drive];
[yourSecondCar wash];
[yourSecondCar getGas];
...




Una classe per lavorare con le frazioni in Objective-C
Supponiamo di volere scrivere un programma per lavorare con le frazioni. Probabilmente avremo bisogno di aggiungere, sottrarre e moltiplicare i nostri valori frazionari. Se non ci fosse stata la programmazione ad oggetti, avremmo scritto qualcosa del genere:

//Simple program to work with fractions
#import <stdio.h>

int main(int argc, char **argv) {
int numerator = 1;
int denominator = 3;
printf("The fraction is %i / %i \n", numerator, denominator);

return 0;
}


Nel programma sopra la frazione e' espressa in termini di numeratore e denominatore. Le prime due linee dichiarono il valore delle variabili numeratore e denominatore, assegnandogli un valore intero, rispettivamente di 1 e 3. Tuttavia, se abbiamo l'esigenze di dover scrivere un centinaio di frazioni, il metodo sopra esposto non e' molto utile ed efficiente. Ogni volta che ci riferiremo ad una particolare frazione, dovremo conoscerne la variabile numeratore e denominatore corrispondente.
Sarebbe stato molto piu' utile definire una frazione come una singola entita', riferendosi a numeratore e denominatore in maniera collettiva con un singolo nome, ad esempio myFraction. Con Objective-C potremo scrivere tutto questo, sfruttando il concetto di classe.

#import <stdio.h>
#import <objc/Object.h>

@interface Fraction: Object {
int numerator;
int denominator;
}
- (void) print;
- (void) setNumerator: (int) n;
- (void) setDenominator: (int) d;

@end

@implementation Fraction;
-(void) print {
printf(" %i / %i ", numerator, denominator);
}

-(void) setNumerator: (int) n {
numerator = n;
}

-(void) setDenominator: (int) d {
denominator = d;
}

@end

int main(int argc, char **argv) {

Fraction *myFraction;

myFraction = [Fraction alloc];
myFraction = [myFraction init];

[myFraction setNumerator: 1];
[myFraction setDenominator: 3];

printf("The value of myFraction is: ");
[myFraction print];
printf("\n");

[myFraction free];

return 0;
}


Il programma e' diviso in tre sezioni:

  • @interface

  • @implementation

  • program

@interface descrive la classe, le sue componenti ed i metodi. @implementation contiene il codice per l'implementazione dei metodi. Infine, la sezione program, contiene il programma che si occupera' di svolgere il reale flusso del nostro programma.
Ognuna di queste sezioni e' una parte fondamentale di ogni programma in Objective-C. Come vedremo, ogni sezione viene rappresentata in un file indipendente. Per quasi tutti i nostri esempi, tuttavia, utilizzeremo una rappresentazione su di un singolo file per ragioni di compatezza.


La sezione @interface
Quando viene definita una nuova classe, bisogna fare un po' di cose. Prima di tutto, bisogno avvisare il compilatore Objective-C sulla parentela della classe che stiamo definendo. In altre parole, dobbiamo specificare da dove proviene la nostra classe, indicando il nome della classe padre.
Secondariamente, bisogna indicare nome e tipo di dati che andranno conservati in quest'oggetto. Questi valori sono chiamati variabili di istanza. Infine, bisogna definire le operazioni che sara' possibile effettuare: i metodi della nostra classe. Tutto questo lavoro, viene fatto in una speciale sezione della nostra classe denominata sezione d'interfaccia o @interface section. Il formato generale di questa sezione e' il seguente:

@interface NewClassName: ParentClassName
{
memberDeclarations;
}

methodDeclarations;
@end


Per convenzione, i nomi di classe iniziano con una lettera maiuscola. Non e' obbligatorio, ma aiuta chi legge il nostro codice a capire di cosa stiamo parlando.


Variabili di istanza
La sezione denominata memberDeclarations specifica che tipo di dati verranno registrati in una frazione, insieme al nome per identificarli. Questa sezione e' racchiusa all'interno di un blocco, delimitato da due parentesi graffe. Nel nostro esempio:

{
  int numerator;
  int denominator;
}


Il blocco memberDeclarations indica che un oggetto frazione ha due membri di tipo intero, chiamati numeratore e denominatore. I membri dichiarati in questa sezione sono conosciuti come variabili di istanza.
Come vedremo, ogni volta che inizializzeremo un nuovo oggetto, verranno creati dei corrispondenti insiemi di variabili di istanza, uno per ogni nuova istanza creata. Se abbiamo due frazioni, una chiamata fracA ed una chiamata fracB, ognuna di esse avra' il suo insieme di variabili di istanza privato; fracA e fracB avranno, quindi, numeratori e denominatori separati. Objective-C tiene traccia per noi di tutto questo.


Metodi di istanza e metodi di classe
Come detto, e' necessario creare dei metodi per poter lavorare con la nostra nuova classe. Ad esempio, sara' necessario creare uno strumento per modificare il valore della frazione. Dal momento che non vogliamo dare/avere accesso alla rappresentazione interna di una frazione - in altre parole, accedere direttamente alle variabili di istanza - bisogna scrivere dei metodi per modicare numeratore e denominatore. Il metodo print, infine, si occupera' di mostrare a schermo il valore della frazione.

-(void) print;


Il segno meno (-) dice ad Objective-C che il metodo e' un metodo di istanza. L'altra opzione disponibile e' un segno piu' (+), che indica un metodo di classe.
Un metodo di classe effettua operazioni sulla classe stessa, come ad esempio quello che crea per noi un'istanza della classe (init). Un metodo di istanza effettua operazioni su una particolare istanza della classe; ottenere valori, modificare valori, effettuare controlli, sono solo alcuni dei compiti che possono essere assegnati ai metodi di istanza.
Ritornando all'esempio delle automobili, dopo aver comprato la nostra auto, sicuramente vorremo fare il pieno di carburante. L'operazione di effettuare rifornimento, viene effettuata su di una particolare macchina, la nostra. Questo e' quello che si chiama metodo di istanza.


Valori di ritorno
Quando viene dichiarato un nuovo metodo, dobbiamo specificare ad Objective-C se il nostro metodo ritorna un valore. In caso positivo, dobbiamo anche specificare di che tipo di valore si tratta. Quest'operazione viene effettuata racchiudendo il tipo di valore di ritorno tra parentesi, dopo il segno (-) o (+):

-(int) getNumerator;


La dichiarazione sopra, specifica che il metodo di istanza getNumerator ritorna un valore intero. Analogamente, la linea:

-(double) getDoubleValue;


dichiara un metodo che ritorna un valore con precisione doppia.
Il valore di ritorno viene inviato dal metodo attraverso la keyword return. Se il metodo non ritorna valori, indicheremo questo comportamento utilizzando il tipo void:

-(void) print;


Questo metodo, dichiara un'azione che non ritorna valori. In questo caso, non e' necessario eseguire la funzione return. Oppure, la si puo' eseguire senza passargli valori:

return;




Argomenti per i metodi
Nella sezione @interface del codice d'esempio scritto sopra, sono presenti due metodi:

- (void) setNumerator: (int) n;
- (void) setDenominator: (int) d;


che non ritornano valori e che prendono un parametro di ingresso. Ognuno dei metodi prende un valore intero come argomento. Questa caratteristica e' indicata dalla keyword (int), specificata prima del nome del parametro n. Il nome del parametro e' arbitrario; serve al metodo per rifersi al valore passato come input.
Riassumendo, la dichiarazione di setNumerator specifica che viene passato un intero di nome n e che non viene ritornato - (void) - nessun valore. Analogo discorso per setDenominator.

Ogni nome del metodo, termina con un due punti (:). Questo indica ad Objective-C che il metodo aspetta un argomento. Dopo il segno di due punti, viene specificato il tipo di parametro aspettato. Infine, viene assegnato un nome simbolico al parametro passato come argomento.
L'intera dichiarazione di metodo, viene terminata con un segno di punto e virgola (;). Vedremo piu' avanti come passare argomenti multipli ai nostri metodi.





La sezione @implementation
La sezione @implementation contiene il codice per i metodi dichiarati in @interface. Nella nostra terminologia, si dice che i metodi vengono dichiarati nella sezione @interface e definiti nella sezione @implementation. Il formato generico per la sezione e' il seguente:

@implementation NewClassName;

methodDefinitions;

@end


NewClassName e' lo stesso nome usato per la sezione @interface. E' possibile specificare il nome della classe padre:

@implementation Fraction: Object;


La sezione methodDefinitions contiene il codice per ciascun metodo presente nella sezione @interface. Similarmente, ogni metodo inizia identificando il suo tipo (classe o istanza), il tipo di ritorno e gli argomenti. Alla fine, anziche' utilizzare il punto e virgola, per concludere la linea, apriamo un blocco logico di istruzioni, utilizzando le parentesi:

@implementation Fraction;
-(void) print
{
printf("%i/%i", numerator, denominator);
}


Il metodo print, utilizza la funzione printf per mostrare a schermo i valori delle variabili di istanza numerator e denominator. Ma a quali variabili di istanza ci riferiamo? Il metodo si riferisce alle variabili contenute nell'oggetto che riceve il messaggio. Questo e' un concetto importantissimo su cui ritorneremo tra breve.
Il metodo setNumerator: prende un argomento intero, chiamato n e conserva il suo valore nella variabile di istanza numerator. Analogamente, setDenominator: conserva il valore del suo argomento d nella variabile di istanza denominator.


La sezione program
La sezione program contiene il codice per risolvere il nostro particolare problema. Vedremo che per programmi piu' complessi, questo codice puo' essere diviso in vari files. Da qualche parte, comunque, e' necessario avere una routine chiamata main. Questa e' la funzione da cui il nostro programma inizia e senza la quale non puo' funzionare.

int main(int argc, char **argv) {

Fraction *myFraction;

myFraction = [Fraction alloc];
myFraction = [myFraction init];

[myFraction setNumerator: 1];
[myFraction setDenominator: 3];

printf("The value of myFraction is: ");
[myFraction print];
printf("\n");

[myFraction free];

return 0;
}


Dentro il main definiamo una variabile chiamata myFraction con la seguente linea:

Fraction *myFraction;


Questa linea indica che myFraction e' un oggetto di tipo Fraction. In altre parole, myFraction viene utilizzato per conservare i valori dalla nuova classe Fraction. L'asterisco (*) davanti al nome e' necessario. Vedremo il suo significato tra poco. Tecnicamente, significa che myFraction e' una referenza ad una frazione.
Adesso che abbiamo un oggetto per conservare la nostra frazione, abbiamo bisogno di crearne una. Questo compito viene svolto dalla seguente linea:

myFraction = [Fraction alloc];


alloc indica la nostra esigenza di allocare memoria per conservare un nuovo oggetto Fraction. L'espressione:

[Fraction alloc]


invia un messaggio alla classe Fraction. Stiamo chiedendo alla nuova classe di applicare il metodo alloc. Da dove viene il metodo alloc? Non ne abbiamo dichiarato uno nella nostra sezione di interfaccia e non ne abbiamo definito uno nella nostra sezione di implementazione.
Il metodo e' stato ereditato dalla classe padre Object. Vedremo piu' avanti di cosa stiamo parlando, in dettaglio.
Per ora, ci basta sapere che quando inviamo un messaggio alloc ad una classe, otteniamo come risposta una nuova istanza della classe. Nel nostro programma, questo valore e' conservato all'interno della variabile myFraction. A questo punto, abbiamo bisogno di inizializzare correttamente il nostro oggetto, prima di poterlo usare. Allocare un oggetto non significa che questo sia pronto per l'uso. Abbiamo bisogno di invocare un metodo di istanza sulla nuova referenza ottenuta.

myFraction = [myFraction init];


Anche in questo caso, stiamo utilizzando un metodo che non abbiamo definito o dichiarato. Il metodo init inizializza le istanze di una classe. Nota che il messaggio e' inviato all'istanza della classe (myFraction). In altre parole, se vogliamo inizializzare uno specifico oggetto Fraction, non inviamo un messaggio alla classe ma all'istanza della nostra classe. Assicuriamoci di aver compreso perfettamente questo punto prima di andare avanti.
Il metodo init, ritorna un valore che verra' conservato all'interno della variabile myFraction. Formalmente, prendiamo il valore di ritorno del metodo init e lo conserviamo nella variabile di riferimento myFraction.

Le due linee di inizializzazione appena descritte, sono utilizzate cosi' spesso in Objective-C che, tipicamente, vengono raggruppate in un'unica riga:

myFraction = [[Fraction alloc] init];


L'espressione piu' interna:

[Fraction alloc]


viene valutata per prima. Il suo valore - la nostra nuova istanza di classe appena creata - viene usata come soggetto per il predicato init. Concludendo, prima allochiamo un nuovo oggetto frazione e dopo ne inizializziamo il suo contenuto. Infine, il risultato viene assegnato alla variabile myFraction.

Adesso siamo pronti per modificare il valore della nostra frazione. Le linee di programma:

[myFraction setNumerator: 1];
[myFraction setDenominator: 3];


fanno esattamente questo. La prima istruzione invia il messaggio setNumerator: a myFraction. L'argomento fornito e' il valore 1. Successivamente, il controllo del nostro programma passa al metodo setNumerator:. Objective-C sa che setNumerator: e' il metodo da usare perche' myFraction e' un oggetto della classe Fraction.
All'interno del codice del metodo setNumerator:, il valore passato nella variabile n, viene conservato all'interno della variabile di istanza numerator. Analogo discorso per quanto riguarda il denominatore della nostra nuova frazione.

Una volta completata la configurazione della frazione, siamo pronti per poterla mostrare a schermo. Questo compito viene svolto dal metodo print:

printf("The value of myFraction is: ");
[myFraction print];
printf("\n");



L'ultima linea del nostro programma:

[myFraction free];


libera la memoria occupata dall'oggetto Fraction. Questa e' una parte delicatissima del nostro codice. Ogni volta che creiamo un oggetto, stiamo chiedendo al compilatore di riservare una porzione di memoria per il nostro oggetto. Quando finiamo di lavorare e' nostra responsabilita' liberare la memoria precedentemente richiesta per successivi usi.
Anche se la memoria verra' rilasciata alla conclusione del programma, dal momento che possiamo effettuare richieste di nuovi oggetti in piu' parti del nostro codice e dal momento che lavoreremo con dispositivi mobili, in cui la memoria e' una caratteristica limitata e critica, dobbiamo effettuare un uso cosciente ed oculato di questa risorsa. Sprecare memoria, rallenta i nostri programmi e puo' farli crashare senza motivo apparente. Ritorneremo su questo punto piu' avanti.


Accesso alle variabili di istanza: Data Encapsulation
Abbiamo visto come i metodi che dialogano con la nostra frazione, accedano direttamente alle variabili di istanza. Un metodo di istanza puo' sempre accedere direttamente alle sue variabili di istanza. Un metodo di classe non puo' farlo.
Ma cosa accade se volessimo accedere a queste variabili da qualche parte, esterna ai metodi di istanza - ad esempio la funzione main del nostro programma sopra? Non e' possibile farlo direttamente perche' queste variabili sono nascoste. Il fatto che esse siano nascoste e' un concetto chiave chiamato data encapsulation. Esso ci permette di scrivere delle definizioni di classe, per estendere o modificare la classe base, senza preoccuparsi di come i programmatori l'abbiano scritta. L'incapsulazione dei dati fornisce uno strato ordinato e pulito per permettere a programmatori diversi di dialogare tra loro.

E' possibile accedere alle variabili di istanza, in maniera pulita, scrivendo dei metodi speciali per ottenere i valori richiesti. Ad esempio, possiamo aggiungere alla nostra classe Fraction due nuovi metodi per ottenere i valori di numerator e denominator.

-(int) numerator;
-(int) denominator;


Le relative implementazioni:

-(int) numerator
{
return numerator;
}

-(int)denominator
{
return denominator;
}


Nota come i nomi dei metodi e le variabili di istanza hanno lo stesso nome. Questo non e' un problema, anzi, e' una pratica comune.
Vedremo piu' avanti, come Objective-C 2.0 permetta di estendere questo importante concetto attraverso l'uso dei sintetizzatori automatici di proprieta'.


Conclusioni
Per concludere, proviamo a vedere come compilare il nostro esempio e visualizzarne l'output a schermo. Apriamo una finestra di terminale e scriviamo:

$ gcc -o outputEsempio1 esempio1.m -lobjc
$
$ ./outputEsempio1

The value of myFraction is: 1 / 3

$



Unica novita', l'utilizzo del flag -lobjc. Questo flag permette al compilatore di riconoscere e compilare correttamente le direttive ad oggetti, presenti nel nostro codice.



Suggerimenti

- Basandoti sull'esempio dell'automobile, pensa ad un oggetto che usi ogni giorno. Identifica una classe per quest'oggetto e scrivi cinque azioni da fare con esso, rispettando la sintassi Objective-C.

-Basandoti sull'esempio della frazione, prova a scrivere un codice che dialoghi con piu' frazioni, effettuando tra esse nuove operazioni di somma, sottrazione e moltiplicazione.



Riferimenti

- iPhone Reference Library: The Object Model
- Programming in Objective-C: Capitolo 3





    lap1