Swift è un linguaggio basato sul paradigma di programmazione ad oggetti. In questo articolo vedremo come definire una classe e come creare istanze o oggetti di una classe. In Swift si preferisce utilizzare il termine "istanze" perchè, come vedremo in seguito, è possibile crearle anche a partire da una struttura, altro concetto fondamentale della programmazione in Swift.
Una classe è un costrutto che permette di definire un nuovo tipo di dati. A differenza di Objective-C, dove per definire una classe vengono utilizzati due file separati, in Swift tutto avviene in un singolo file. Nella dichiarazione di una classe sono generalmente definite un insieme di proprietà e di metodi, nonchè uno o più inizializzatori. Vediamone subito un esempio:
class Persona {
let nome: String
let cognome: String
var secondoNome: String?
func nomeCompleto() -> String {
if let altroNome = secondoNome {
return self.nome + " " + altroNome + " " + self.cognome
} else {
return self.nome + " " + self.cognome
}
}
init(nome: String, cognome: String) {
self.nome = nome
self.cognome = cognome
}
}
Nell'esempio precedente abbiamo definito il nuovo tipo Persona, un tipo di dato astratto che ci permette di modellare una persona con alcune sue caratteristiche (proprietà) es azioni (metodi) in Swift.
Proprietà e metodi
Le proprietà sono definite come costanti o variabili, mentre i metodi sono definiti come funzioni. Uno speciale metodo, il cui nome è **init**
, è incaricato di inizializzare correttamente le proprietà. Nell'esempio precedente, tuttavia, la variabile secondoNome
non è inizializzata nel metodo init
, in quanto una variabile di tipo opzionale è inizializzata di default al valore nil
. Per le altre variabili non opzionali, qualora non fossero state inizializzate nell'inizializzatore (o al momento della definizione), il compilatore Swift avrebbe generato un errore a tempo di compilazione.
Dato che una classe definisce un tipo di dati, è possibile creare una o più istanze a partire da quella data classe. Di seguito un esempio in cui creiamo due istanze del tipo Persona
:
let p1 = Persona(nome: "Antonio", cognome: "Calanducci")
var p2 = Persona(nome: "Steve", cognome: "Jobs")
Come si evince dall'esempio precedente, viene utilizzato il nome della classe al quale vengono passati i parametri definiti nel suo inizializzatore. A partire da un'istanza è possibile accedere ai valori delle sue proprietà usando la dot notation:
print(p1.nome)
print(p2.cognome)
Se una proprietà è di tipo variabile, allora se ne può anche modificare il valore:
p1.secondoNome = "Salvatore"
Il lettore attento si chiederà come è possibile invocare un'operazione di assegnazione su una istanza dichiarata come costante (sopra abbiamo usato il let
). Ciò è possibile poichè non viene modificato il valore della costante p1
, che è un riferimento all'istanza, la quale a sua volta contiene una proprietà di tipo variabile.
In maniera simile, possiamo invocare un metodo su un'istanza, usando la stessa sintassi della dot notation:
print(p1.nomeCompleto())
Istanze e self
Grazie all'uso degli inizializzatori possiamo generare istanze diverse a partire dalla definizione di una classe. Ogni istanza mantiene una copia separata dei valori delle sue proprietà. Per cui la modifica della proprietà nome
di p1
non influenzerà il valore della rispettiva proprietà dell'istanza p2
. Per far rifermento alla proprietà di una istanza all'interno della classe si usa la parola speciale self
. Essa indica l'istanza che verrà creata a partire dalla classe in oggetto. Abbiamo già visto nell'esempio precedente l'uso di self
sia nell'inizializzatore che nel metodo nomeCompleto
.
Tipi valore e tipi referenza
Tutti i tipi di dato che abbiamo usato nelle lezioni precedenti erano tipi valore, cioè venivano copiati quando assegnati a variabili/costanti, o passati come argomenti di funzione. Le classi, invece, sono tipi referenza: durante un'assegnazione ad una nuova variabile o funzione, non verrà creata una copia dell'istanza, bensì ne verrà copiato il riferimento. Per chi proviene da linguaggi di programmazione come il C o il C++, possiamo pensare ad un riferimento come ad un puntatore, anche se qui non è necessario utilizzare l'operatore *
per accedere al valore del riferimento. Facciamo un esempio:
let p3 = p1
p3.secondoNome = "Salvo"
print(p1.secondoNome!) // verrà stampato "Salvo"
Di fatto, p1
e p3
sono due riferimenti alla medesima istanza, come dimostrato dal fatto che modificando la proprietà secondoNome
di p3
risulterà ugualmente variata anche la stessa propietà di p1
.
Operatore di identità
È possibile verificare se due variabili o costanti fanno riferimento alla stessa istanza tramite l'operatore di identità ===
. Esiste anche l'operatore opposto !==
. Continuando con l'esempio precedente, possiamo accertarci che p1
e p2
fanno riferimento alla stessa istanza come segue:
p1 === p3 // otterremo true
Il playground con tutti gli esempi sulle classi discussi fin qui è disponibile a questo indirizzo.