Wikibooks:Deposito/Moduli/Objective-C/Classi e oggetti

Wikibooks, manuali e libri di testo liberi.

Objective-C introduce nel linguaggio C il supporto all'uso di entità denominate oggetti. Un oggetto raggruppa in sé dati e operazioni relative ad un unico elemento concettuale; ad esempio, un oggetto "data" può raggruppare in sé sia i dati relativi al giorno, mese e anno in oggetto, sia operazione relative ad essi (ad es. calcolare la differenza di giorni tra quella data e un'altra).

Objective-C supporta il concetto di classe di un oggetto. Una classe definisce le caratteristiche di un insieme di oggetti omogenei (per esempio, la classe degli oggetti "data" può definire caratteristiche di questi oggetti, come ad esempio "contiene giorno, mese e anno" oppure "può essere calcolata una differenza tra date"). In Objective-C ogni classe può definire un insieme di variabili di istanza, che contengono i dati che gli oggetti di quella classe possono gestire, e di metodi o messaggi, che rappresentano le operazioni a cui gli oggetti di quella classe possono essere sottoposti.

Le classi sono organizzate in una gerarchia, laddove esistono superclassi e sottoclassi. Una sottoclasse è un'estensione di una classe esistente; gli oggetti della sottoclasse si comportano esattamente come gli oggetti della superclasse, con l'unica differenza che la sottoclasse può definire dei comportamenti alternativi quando vengono compiute determinate operazioni. Ad esempio, la classe degli oggetti "data" può avere come sottoclassi "data del calendario gregoriano", "data del calendario giuliano" o "data del calendario lunare"; ognuna di esse può modificare, sempre ad esempio, la modalità con cui viene calcolato il risultato quando viene richiesta una differenza tra date. Tutte le classi hanno una superclasse, tranne quelle denominate classi radice; esse definiscono i comportamenti che tutti gli oggetti possiedono. A differenza di altri linguaggi, in Objective-C possono esistere più classi radice; nel prosieguo sfrutteremo una generica classe 'Object' quale radice di tutte le gerarchie delle classi che definiremo, ma l'uso di una classe radice dipende dall'ambiente in cui ci si ritrova a programmare e dalle librerie disponibili.

Definire una classe in Objective-C[modifica]

Il primo passo verso l'uso di Objective-C è la definizione di una classe. Le classi sono divise in due metà, interfaccia e implementazione: la prima definisce quali sono le operazioni a cui possono essere sottoposti gli oggetti di quella classe, mentre la seconda contiene il codice che viene eseguito all'atto dell'esecuzione di tali operazioni. Esse corrispondono al "prototipo" di una funzione e al codice di quella stessa funzione nel normale linguaggio C.

L'interfaccia di una classe si può introdurre con la direttiva @interface, che ha la seguente sintassi:

 @interface [Nome della classe] : [Nome della superclasse] {
   [Variabili di istanza]
 }
 
 [Messaggi]
 
 @end

L'interfaccia definisce:

  • Come si chiama la classe? Il nome di una classe deve essere compatibile con gli identificatori C, ovvero può iniziare con una lettera o il simbolo di sottolineatura (underline) '_' e può contenere solo lettere, numeri e simboli di sottolineatura (underline).
  • In quale livello della gerarchia mi pongo? Qual è la mia superclasse? Se vogliamo creare una classe nuova e non una sottoclasse di una già esistente, il nome della superclasse sarà quello della classe radice alla cui gerarchia vogliamo appartenere (ad esempio Object). Tutti i dati e le operazioni definite per la superclasse saranno utilizzabili anche nella sottoclasse.
  • Quali dati può contenere ciascun oggetto? Tali dati sono registrati nelle variabili di istanza, ovvero in variabili che vengono create quando si genera un oggetto e rimosse quando lo si rimuove dalla memoria. Le variabili di istanza si dichiarano utilizzando la normale sintassi C per la dichiarazione delle variabili, ma non possono essere inizializzate (ovvero int numero; è utilizzabile, ma int numero = 2; non lo è).
  • Quali operazioni possono essere effettuate? Ogni messaggio o metodo rappresenta un'operazione effettuabile sugli oggetti di questa classe. La sintassi è alquanto differente dalle altre dichiarazioni possibili nel linguaggio C, e sarà specificata più avanti.

Un esempio di interfaccia per la classe degli oggetti "data" specificata sopra può essere:

 @interface Data : Object {
 }
 
 - (id) differenzaTraMeELaData:(id) data;
 @end

Osserviamo che:

  • La classe si chiama Data, un identificatore C valido. È sempre necessario ricordarsi che l'identificatore può entrare in conflitto con altri identificatori C dichiarati (ad esempio quelli di variabili o funzioni) e bisogna assicurarsi che nel contesto corrente sia "unico"; in questo caso, che non esistano classi, variabili o funzioni denominate Data.
  • La superclasse di Data si chiama Object. Nel seguito presumeremo che tale classe sia una classe radice resa disponibile dal nostro compilatore o ambiente di programmazione.
  • Gli oggetti di classe Data non contengono dati, ovverosia non hanno variabili di istanza. La lista delle variabili di istanza è vuota. (Le parentesi graffe sono necessarie anche se non si dichiara alcuna variabile di istanza.)
  • Gli oggetti di classe Data possono essere sottoposti al metodo chiamato differenzaTraMeELaData:, che ritorna un oggetto generico (id) e accetta un singolo parametro, di nome data, anch'esso di tipo oggetto generico. Il significato e la sintassi di questa linea sono esaminati più sotto.

Messaggi e oggetti. Creare un oggetto[modifica]

L'unica altra grande aggiunta alla sintassi C da parte di Objective-C è la sintassi per l'invio di messaggi agli oggetti. Un messaggio è simile alla chiamata di un metodo in un altro linguaggio a oggetti, e può certamente essere vista come tale; però, in Objective-C, tutte le operazioni che in altri linguaggi a oggetti sono svolte ad esempio da operatori (come ad es. new in Java) vengono effettuate inviando messaggi a oggetti.

Per inviare un messaggio ad un oggetto, dato oggetto un puntatore ad un oggetto particolare, si utilizza la seguente sintassi:

[oggetto messaggio];

Questa sintassi si può utilizzare in qualsiasi posizione in cui si può inserire una chiamata a funzione C normale. Il messaggio ha una forma molto differente rispetto a una normale chiamata a funzione C, ispirata dal linguaggio Smalltalk; in C, una funzione viene invocata inserendo il nome completo della funzione e successivamente la lista di parametri, tra parentesi, mentre un messaggio Objective-C "intercala" il nome e i parametri, ponendo una porzione del nome prima di ciascun parametro. I parametri sono preceduti dal carattere :.

Ad esempio, i seguenti sono messaggi validi:

  • Senza parametri.
[oggetto messaggio];

  • Con un singolo parametro.
[oggetto messaggio:var1];

(con var1 un'espressione qualunque, come in un parametro di una funzione C. Da notare che il nome di questo messaggio è messaggio:, diverso dal precedente messaggio. Un oggetto può avere due metodi differenti che usino questi nomi, con codice differente, senza che si generi conflitto.)

  • Con più parametri.
 [oggetto messaggioConPrimoParametro:var1 secondoParametro:var2 terzoParametro:var3];

(con var1, var2, var3 tre espressioni qualunque. Da notare che il nome del messaggio in questo caso è messaggioConPrimoParametro:secondoParametro:terzoParametro:.)

Ma come si fa a ottenere un puntatore ad un oggetto, come quello sopra indicato? Come si fa a creare un nuovo oggetto?

La risposta è semplice: in Objective-C, anche le classi sono oggetti e possono ricevere messaggi proprio come i normali oggetti. Ad esempio la nostra precedente classe Data, una volta dichiarata, dà luogo ad un oggetto chiamato Data al quale possiamo inviare messaggi. Inviando un particolare messaggio ad una classe, è possibile creare oggetti di quella classe ed operare su di essi. Tale messaggio sostituisce l'operatore new di Java e di altri linguaggi a oggetti.

La domanda successiva è, ovviamente, qual è questo messaggio? La risposta non è immediata, perché dipende dall'ambiente in cui ci si trova a lavorare: il messaggio fa infatti di solito parte della classe radice, fornita dall'ambiente di programmazione, e sia la classe che l'ambiente non sono univoci (ne esistono di differenti). Una sottoclasse, oltre a consentire ai propri oggetti di essere sottoposti a tutte le operazioni (ricevere tutti i messaggi) che la superclasse dichiara, eredita anche tutti i messaggi a cui l'oggetto-classe può essere sottoposto.

Per semplificare le cose, molte librerie seguono una convenzione per la quale gli oggetti-classe possono ricevere un messaggio chiamato new. Il risultato di questo messaggio è un nuovo oggetto della classe a cui il messaggio è stato inviato.

Ad esempio:

 id data = [Data new];

crea un nuovo oggetto di classe Data e pone un puntatore ad esso nella variabile data. Il tipo id è un tipo di dato generico simile a void*, fornito da molti compilatori Objective-C, che può contenere un puntatore a un oggetto di una classe qualsiasi.

Aggiungere metodi a una classe[modifica]

Torniamo all'esempio precedente della classe Data:

 @interface Data : Object {
 }
 - (id) differenzaTraMeELaData:(id) data;
 @end

Vediamo di dissezionare la linea che definisce il messaggio differenzaTraMeELaData:. La sintassi è semplice:

[+|-] (tipo di ritorno) primaParteDelNome:(tipo) nomeParametro parte2:(tipo) param parte3:(tipo) param;

dove [+|-] indica un carattere '+' oppure '-', "tipo di ritorno" è il tipo del valore ritornato, e il nome è suddiviso in modo simile a come sarà nella chiamata. Ogni parametro è indicato con un tipo e un nome unico (a differenza dei prototipi di funzioni C nelle quali viene indicato solo il tipo del parametro e non il nome).

Il primo carattere indica quale oggetto può ricevere il messaggio indicato. Se è '-', allora il metodo sarà ricevuto dagli oggetti di questa classe (cosiddetti messaggi di istanza); se è '+', invece, sarà l'oggetto associato alla classe (quello con lo stesso nome della classe in oggetto) a poterlo ricevere; questo tipo di messaggio è detto messaggio di classe. È possibile avere messaggi di istanza e di classe con ugual nome senza che vi siano conflitti (il compilatore "sa" a quale messaggio ci si riferisce a seconda di quale sia l'oggetto o la classe a cui lo si manda).