int: Rappresenta un numero, può anche essere negativo
float, double: Numeri decimali, i double hanno 15 cifre decimali mentre i float 6
string: Testo
bool: True o False
Ovviamente possiamo creare anche tipi personalizzati.
Type Inference
Swift nella maggior parte dei casi capisce da solo il tipo della variabile che stiamo dichiarando, anche se noi lo omettiamo
Variabili e Costanti
Le variabili possono cambiare nel tempo
Le costanti sono dati, appunto, costanti nel tempo. Possiamo usarle per ottenere un codice più efficiente
var highscore = 0 // Dichiarazione di variabilelet highscore = 0 // Dichiarazione di costante
Se vogliamo specificare il tipo di una variabile utilizziamo:
var myName: String = "Alessio"
Array
Sono liste ordinate di elementi, ogni elemento è accessibile da un indice.
var numbers = [21, 55, 19, 47, 22, 37, 88, 71]
Anche in questo caso swift capisce che contiene interi, ma se dobbiamo creare un array vuoto da riempire successivamente possiamo scrivere:
var numbers: [Int] = []
Alcuni metodi utili per gli array:
array.count - restituisce il numero di elementi
array.first - restituisce il primo elemento
array.last - restituisce l’ultimo elemento
Optional
Da notare che .last restituisce un Optional che sono indicato da un ? ad esempio un intero optional sarà Int?. Questo significa che il dato potrebbe non esistere, ad esempio l’ultimo elemento di un array vuoto potrebbe non esistere appunto.
Il tipo Optional verrà approfondito più avanti
Possiamo accedere agli elementi tramite gli indici, quindi ad esempio array[0] prende il primo elemento.
array.append(elem) - aggiunge l’elemento in ultima posizione
array.insert(elem, at) - aggiunge l’elemento elem nella posizione at
array.sort() - ordina l’array
array.reverse() - riordina al contrario l’array
array.shuffle() - riordina in modo casuale l’array
Set
I set sono simili agli array perché contengono ordinamenti ma non hanno ordinamento e non possono avere elementi duplicati.
Il vantaggio principale è che le operazioni sono molto più veloci rispetto agli array.
Si dichiara in questo modo:
var numbersSet: Set<Int> = [] // Set vuoto di Interi
Possiamo anche crearne uno basato su un array già esistente:
Da notare che i duplicati presenti nell’array verranno cancellati e inoltre ogni volta avrà un ordinamento diverso.
Hashable
Per permettere le operazioni del set il tipo contenuto deve essere hashable ovvero deve esistere una funzione che ci restituisce un numero univoco che identifica l’oggetto (a grandi linee 😼).
I tipi di base hanno già questa caratteristica quindi dobbiamo assicurarci di garantirla per nostri eventuali tipi custom.
Condivide molte operazioni di base con gli array, come ad esempio:
set.contains(elem) - Restituisce un booleano che ci dice se l’elemento è contenuto nel set
set.innsert(elem) - Inserisce l’elemento nel set, qui non ci serve una posizione dato che non c’è ordinamento
Dizionari
Un dizionario è un insieme di elementi dove ogni elemento è una coppia formata da (chiave, valore), li dichiariamo in questo modo:
let devices: [String: String] = [ "phone": "iPhone 16", "laptop": "MacBook Air M2", "desktop": "Ryzen 7 5800X"]
Quindi dobbiamo definire sia il tipo della chiave che del valore.
Attraverso una chiave possiamo accedere al suo valore in tempo costante:
devices["phone"] // Restituirà "iPhone 16" in questo caso
Funzioni
Sono dei pezzi di codice che possiamo riutilizzare semplicemente chiamandoli. Prima ovviamente vanno definiti.
func printName() { print("Alessio Marini")}
Per far funzionare la funziona ci basta chiamarla all’interno del codice del programma:
printName()
Possiamo anche passare dei parametri e lavorarci sopra, ad esempio:
func printName(name: String) { print(name)}// E chiamare nel main:printName("Alessio Marini")
La funzione può anche ritornare valori:
func add(firstNumber: Int, to secondNumber: Int) -> Int { return firstNumber + secondNumber}// La invochiamo conadd(5, 3) // Restituisce il valore quindi non lo stampa// Per stamparlo possiamo fare ad esempio:var somma = add(5, 3)print(somma)
Il to nella segnatura
Serve semplicemente per migliorare la leggibilità del codice, infatti possiamo chiamare la funzione anche con:
add(firstNumber: 18, to: 126)
Serve quindi a rendere più leggibile a noi umani il “call site” della funzione ovvero dove la chiamiamo ma non è essenziale.
Possiamo quindi dare una sorta di “alias” ai parametri.
IF / ELSE
…
FOR Loops
Ci permette di ripetere un’azione tante volte, possiamo anche usarli per iterare su delle collezioni, ad esempio:
let names = ["James", "Davis", "Harden"]for name in names { print(name)}
Si possono anche filtrare le iterazioni:
for name in names where name == "Harden" { print(name)}
Possiamo anche iterare su un range di valori:
for i in 0..<25 {}
Range
Si usano i range, hanno diverse formattazioni:
0...n - va da 0 ad n compreso
0..<n - va da 0 ad n escluso
Si possono usare anche senza un estremo:
..<2 - contiene tutti i valori da 2 a −∞
2... - contiene tutti i valori da 2 a ∞
I range possono essere usati anche per accedere a un range di valori di una collezione, ad esempio:
let languages = ["Swift", "Java", "C"]print(languages[0...2])
Ad esempio possiamo riempire un array con numeri casuali in questo modo:
var randomInts: [Int] = []for i in 0..<25 { let randomNumber = Int.random(in: 0...100) randomInts.append(randomNumber)}
Enum
Rappresentano un insieme di valori
enum Phone { case iPhone11Pro case iPhoneSE case pixel case onePlus}
Si possono anche dichiarare tutti su una linea.
Per utilizzarli creiamo una funzione che prende come input il tipo enum appena creato:
func getAlemOpinion(on phone: Phone) { if phone == .iPhone11Pro { print("Something about 11 Pro") } else if phone == .iPhoneSE { print("Something about iPhhoneSE") } else if ... else ...}
Adesso quando la chiamiamo possiamo passare come parametro soltanto uno dei tipi dell’enum, va passato con un . davanti quindi, ad esempio:
getAlemOpinion(on: .pixel)
Raw Value
Possiamo dare un tipo “raw” alle enum ovvero un valore associato all’enum, ad esempio:
enum Phone: String { case iPhone11Pro = "Pro Model of Apple" case iPhoneSE = "Economic Model of Apple" case pixel = "Google Phone" case onePlus = "Another Android phone"}
Possiamo rendere più pulito, ad esempio, il codice scritto primo con tutti gli if / else a cascata:
func getAlemOpinion(on phone: Phone) { switch phone { case .iPhone11Pro: print("Something about 11 Pro") case .iPhoneSE: print("Something about iPhoneSE") case ... default: print("Non hai inserito un telefono valido") }}
Il caso default viene eseguito quando non rientriamo nei casi precedenti.
È importante gestire tutti i casi in uno switchcase
Operators
…
Optionals
Ci permettono di gestire la “mancanza” di valore per un tipo di dato, ad esempio
var ages: [Int] = []ages.sort()let oldestAges = ages.last
In questo caso last non esiste dato che l’array è vuoto, ritornerà il valore nil
Di base non possiamo accedere direttamente al valore contenuto in un Optional, se presente, ma dobbiamo usare dei costrutti che ci permettono di effettuare l’unwrap:
If let
In questo modo stiamo dicendo, se ages.last ha un valore e non è nil allora lo inseriamo in oldestAge, abbiamo accesso alla variabile all’interno delle parentesi
if let oldestAge = ages.last { print("The oldest age is \(oldestAge)")} else { print("There is no oldest age. You must have no students.")}
Nil coalescing
Creiamo la variabile, se ha un valore allora lo usiamo altrimenti forniamo un valore di default dopo i ??
let oldestAge = ages.last ?? 999
Guard Statement
Ci permette di uscire dal codice o eseguire delle operazioni nel caso in cui il valore è nil.
Con if let abbiamo accesso alla variabile soltanto all’interno dello scope di if let quindi se dobbiamo controllare più tipi potremmo avere un codice poco leggibile e pieno di parentesi annidate.
Con guard possiamo controllare il tipo in una riga e poi usare la variabile all’interno dell’intera funzione, quindi anche se controlliamo più variabili abbiamo comunque accesso a tutte.
Force Unwrap
Questo è molto pericoloso, non ci interessa che valore ha il dato, lo usiamo e basta.
let oldestAge = ages.last!
Self
Si usa con le classi serve a riferirsi all’oggetto in cui ci troviamo, praticamente uguale al this di Java
Classi
Sono la base della programmazione ad Oggetti. Possiamo quindi creare i nostri tipi personalizzati:
class Developer { var name: String var jobTitle: String var yearsExp: Int init(name: String, jobTitle: String, yearsExp: Int) { self.name = name self.jobTitle = jobTitle self.yearsExp = yearsExp }}
Possiamo creare degli oggetti in questo modo:
let alessio = Developer(name: "Alessio", jobTitle: "Student", yearsExp: 2)
Ed accedere ai campi come le altre variabili:
alessio.namealessio.jobTitlealessio.yearsExp
Se vogliamo crearne uno vuoto dobbiamo rendere i campi della classe degli Optionals, quindi:
class Developer { var name: String? var jobTitle: String? var yearsExp: Int? init(name: String, jobTitle: String, yearsExp: Int) { self.name = name self.jobTitle = jobTitle self.yearsExp = yearsExp }}
In questo modo possiamo creare l’oggetto senza parametri e poi aggiungerli successivamente.
Ereditarietà
Una classe può ereditare elementi di un’altra classe
Una sottoclasse si crea come una classe normale ma dobbiamo indicare la superclasse con i :, quindi:
class Developer { var name: String? var jobTitle: String? var yearsExp: Int? init(name: String, jobTitle: String, yearsExp: Int) { self.name = name self.jobTitle = jobTitle self.yearsExp = yearsExp } func speakName() { print(name!) }}class iOSDeveloper: Developer { var favoriteFramework: String? func speakFavoriteFramework() { if let favoriteFramework == favoriteFramework { print(favoriteFramework) } else { print("No favorite Framework") } }}
E possiamo creare un oggetto:
let alessio = iOSDeveloper(name: "Alessio"...)
Usiamo quindi lo stesso costruttore della classe Developer che viene ereditato da iOSDeveloper.
Possiamo modificare delle funzionalità della superclasse tramite gli override:
class Developer { var name: String? var jobTitle: String? var yearsExp: Int? init(name: String, jobTitle: String, yearsExp: Int) { self.name = name self.jobTitle = jobTitle self.yearsExp = yearsExp } func speakName() { print(name!) }}class iOSDeveloper: Developer { var favoriteFramework: String? func speakFavoriteFramework() { if let favoriteFramework == favoriteFramework { print(favoriteFramework) } else { print("No favorite Framework") } } override func speakName() { print("\(name!) - \(jobTitle!)") }}
Struct
Sono simili alle classi, infatti possono avere proprietà e funzionalità, la differenza è che sono più leggere delle classi perché sono dei value types mentre le classi sono reference types.
Reference Types - Essenzialmente sono come dei puntatori che puntano alla struttura dati creata.
Value Types - Sono loro stessi l’istanza di quei dati.
Infatti se ad esempio creiamo una nuova variabile e ci assegniamo un oggetto già creato, questa variabile sarà in realtà un puntatore a quello stesso oggetto, ovvero:
Servono ad aggiungere funzionalità ad un tipo di dato già esistente.
extension String { func removeWhitespace() -> String { return components(separatedBy: .whitespaces).joined() }}let alphabet = "A B C D E F"print(alphabet.removeWhitespace()) // ABCDEF