Introduzione a Swift parte 2

Nel primo articolo di questa serie introduttiva su Swift, abbiamo parlato della filosofia di Swift, abbiamo dato una prima occhiata alla sua sintassi e evidenziato alcune differenze chiave con Objective-C. In questo articolo, continuiamo la nostra esplorazione della sintassi di Swift. Potrai anche conoscere gli optionals e vedere come funziona la gestione della memoria in Swift.

1. Conditionals e Loop

Se

Se le affermazioni sono identiche in Swift e Objective-C con l'eccezione di due sottili differenze:

  • le parentesi attorno alla variabile condizione sono facoltative
  • sono necessarie le parentesi graffe

Queste sono le uniche differenze con le affermazioni in Objective-C.

Intervalli

Come abbiamo visto nel primo articolo, Swift include due operatori di intervallo ... < e ... per specificare un intervallo di valori. Questi due operatori sono i operatore della gamma semichiuso e il operatore a raggio chiuso.

Un intervallo semichiuso, ad esempio 1 ... <5, rappresenta i valori 1, 2, 3 e 4, escluso 5. Un intervallo chiuso, come 1 ... 5, rappresenta i valori 1, 2, 3, 4 e include 5.

Gli intervalli possono essere utilizzati in per loop, schieramento pedice, e anche in interruttore dichiarazioni. Dai uno sguardo ai seguenti esempi.

// per esempio di loop per i in 1 ... <10   // iterates from 1 to 9
// esempio di indice di matrice lascia someArray = ["apple", "pair", "peach", "watermelon", "strawberry"] per fruit in someArray [2 ... <4]  println(fruit)  // outputs: peach and watermelon
// cambia l'esempio switch someInt case 0: // fai qualcosa con 0 case 1 ... <5: // do something with 1,2,3,4 case 5… 10: // do something with 5,6,7,8,9,10 default: // everything else 

Interruttore

Le istruzioni switch sono più potenti in Swift di quanto non lo siano in Objective-C. In Objective-C, il risultato dell'espressione di un'istruzione switch deve essere di tipo intero e i valori di ciascuna istruzione case devono essere un'espressione costante o costante. Questo non è vero in Swift. In Swift, le dichiarazioni del caso possono essere di qualsiasi tipo, compresi gli intervalli.

In Swift, l'interruttore ha no rompere dichiarazioni e non passa automaticamente da un caso all'altro. Quando si scrive un'istruzione switch, è necessario prestare attenzione che tutte le condizioni siano gestite dalle sue istruzioni case, in caso contrario si verificherà un errore del compilatore. Un modo sicuro per coprire tutte le condizioni è includere a predefinito dichiarazione di un caso.

Ecco un esempio di a interruttore dichiarazione con Stringa casi:

lascia verdura = "peperoncino" commuta vegetale case "sedano": lascia vegetaleComment = "Aggiungi un po 'di uvetta e fai le formiche su un tronco." caso "cetriolo", "crescione": let vegetableComment = "Sarebbe un buon panino al tè". default: let vegetableComment = "Tutto ha un buon sapore in zuppa".  

In Swift, le dichiarazioni del caso non vengono visualizzate per impostazione predefinita. Questa è stata una decisione progettuale intenzionale per evitare errori comuni. Se un caso specifico deve cadere, puoi usare il sfumare parola chiave per indicarlo al compilatore.

switch someInt case 0: // fai qualcosa con 0 case 1: // fai qualcosa con 1 case 2: // fai qualcosa con 2 fallthrough default: // fai qualcosa per tutto il resto // caso 2 passerà a default Astuccio

Non si ferma qui. Swift aggiunge altre due funzionalità per cambiare, legami di valore e il dove clausola. Il valore vincolante viene utilizzato con caso lasciato parole chiave per associare una costante al caso corrispondente. La clausola where aggiunge una condizione aggiuntiva all'istruzione case usando il dove parola chiave.

Questi due concetti sono meglio spiegati con esempi. Il seguente blocco di codice mostra come valore vincolante lavori.

let somePoint = (xaxis: 2, yaxis: 0) scambia somePoint case (lascia x, 0): println ("sull'asse x con un valore x di \ (x)") caso (0, sia y): println ("sull'asse y con un valore y di \ (y)") case let (x, y): println ("da qualche altra parte a (\ (x), \ (y))")

La prima dichiarazione del caso, caso (lascia x, 0), corrisponderà ai valori dove asseY è uguale a 0 e qualsiasi valore per Xaxis, e ci leghiamo Xaxis alla costante X da utilizzare all'interno della dichiarazione del caso.

Ecco un esempio della clausola where in action.

lasciare vegetale = "peperoncino" commutare vegetale caso "sedano": println ("Aggiungere un po 'di uva passa e fare le formiche su un tronco.") caso lasciare x dove x.hasSuffix ("pepe"): println ("Sono allergico a \ (x) ") default: println (" Tutto ha un buon sapore in zuppa. ") // output: sono allergico al peperone rosso

2. Funzioni e chiusure

funzioni

Definire funzioni e chiusure è facile in Swift. Gli sviluppatori di Objective-C li conoscono meglio come funzioni e blocchi.

In Swift, i parametri di funzione possono avere valori predefiniti, che ricordano i linguaggi di scripting, come PHP e Ruby.

La sintassi per le funzioni è la seguente:

func functionName (parameterName: Type = DefaultValue) -> returnType [...] return returnType; 

Per scrivere a di Ciao funzione che accetta un parametro nome di tipo Stringa e restituisce a Bool in caso di successo, scriviamo quanto segue:

func sayHello (name: String) -> Bool println ("ciao \ (nome)"); ritorna vero;  sayHello ("Mondo") // output // ciao Mondo

Per passare un valore predefinito per nome parametro, l'implementazione della funzione dovrebbe essere la seguente:

func sayHello (name: String = "World") -> Bool println ("ciao \ (nome)"); ritorna vero;  sayHello () // output // ciao World sayHello ("mike") // output // ciao mike

Una caratteristica che è completamente assente in Objective-C lo è tuple. In Swift, le funzioni possono restituire più valori sotto forma di una tupla. Le tuple sono trattate come una singola variabile, il che significa che puoi passarle come una variabile.

Le tuple sono molto facili da usare. In effetti, abbiamo già lavorato con le tuple nel precedente articolo quando abbiamo enumerato un dizionario. Nel prossimo snippet di codice, la coppia chiave / valore è una tupla.

for (chiave, valore) in someDictionary println ("Chiave \ (chiave) ha valore \ (valore)"

Come vengono utilizzate le tuple e in che modo ne trarrai beneficio? Diamo un'occhiata a un altro esempio. Modifichiamo quanto sopra di Ciao funzione per restituire un booleano in caso di successo e il messaggio risultante. Lo facciamo restituendo una tupla, (Bool, String). L'aggiornato di Ciao la funzione si presenta così:

func sayHello (name: String = "World") -> (Bool, String) let greeting = "ciao \ (nome)" return (true, greeting);  let (successo, saluto) = sayHello () println ("sayHello ha restituito successo: \ (successo) con saluto: \ (saluto)"); // output // sayHello ha restituito successo: 1 con saluto: ciao Mondo 

Le tuple sono state nella lista dei desideri di molti programmatori Objective-C per molto tempo.

Un'altra caratteristica interessante delle tuple è che possiamo dare un nome alle variabili restituite. Se rivisitiamo l'esempio precedente e diamo nomi alle variabili della tupla, otteniamo quanto segue:

func sayHello (name: String = "World") -> (success: Bool, greeting: String) let greeting = "ciao \ (nome)" return (true, greeting);  let status = sayHello () println ("sayHello ha restituito successo: \ (status.success) con il saluto: \ (status.greeting)"); // output // sayHello ha restituito successo: 1 con saluto: ciao Mondo 

Ciò significa che invece di definire una costante separata per ogni elemento di ritorno di una tupla, possiamo accedere agli elementi di tupla restituiti usando la notazione dei punti come mostrato nell'esempio precedente, status.success e status.greeting.

chiusure

Le chiusure in Swift sono le stesse dei blocchi in Objective-C. Possono essere definiti in linea, passati come parametri o restituiti dalle funzioni. Li usiamo esattamente come useremmo i blocchi in Objective-C.

Definire le chiusure è anche facile. In realtà, una funzione è un caso speciale di chiusure. Quindi non c'è da meravigliarsi che definire una chiusura assomigli molto alla definizione di una funzione.

Le chiusure sono di prima classe, il che significa che possono essere passate e restituite da funzioni come qualsiasi altro tipo, ad esempio IntStringaBool, ecc. Le chiusure sono essenzialmente blocchi di codice che possono essere chiamati in seguito e hanno accesso allo scope in cui sono stati definiti.

Creare una chiusura senza nome è semplice come avvolgere un blocco di codice in parentesi graffe. I parametri e il tipo di ritorno della chiusura sono separati dal corpo della chiusura con il nel parola chiave.

Diciamo che vogliamo definire una chiusura che ritorna vero se un numero è pari, allora quella chiusura potrebbe assomigliare a qualcosa:

let isEven = (number: Int) -> Bool in let mod = number% 2 return (mod == 0)

Il è anche la chiusura richiede un Int come parametro singolo e restituisce a Bool. Il tipo di questa chiusura è (numero: Int) -> Bool, o (Int -> Bool) in breve. Possiamo chiamare è anche in qualsiasi parte del nostro codice, proprio come invocheremmo un blocco di codice in Objective-C.

Per passare una chiusura di questo tipo come parametro di una funzione, usiamo il tipo di chiusura nella definizione della funzione:

let isEven = (number: Int) -> Bool in let mod = number% 2; return (mod == 0);  func verifyIfEven (numero: Int, verificatore: (Int-> Bool)) -> Bool return verifier (numero);  verifyIfEven (12, isEven); // restituisce true verifyIfEven (19, isEven); // restituisce false

Nell'esempio sopra, il verificatore parametro del verifyIfEven la funzione è una chiusura che passiamo alla funzione.

3. Classi e strutture

Classi

È tempo di parlare della pietra miliare della programmazione orientata agli oggetti, delle classi. Le classi, come accennato prima, sono definite in un singolo file di implementazione con a .veloce estensione. Le dichiarazioni e i metodi di proprietà sono tutti definiti in quel file.

Creiamo una lezione con il classe parola chiave seguita dal nome della classe. L'implementazione della classe è racchiusa in un paio di parentesi graffe. Come in Objective-C, la convenzione di denominazione per le classi è quella di utilizzare il caso cammello superiore per i nomi delle classi.

class hotel // properties // functions

Per creare un'istanza di Hotel classe scriviamo:

lasciare h = Hotel ()

In Swift, non è necessario chiamare dentro sugli oggetti come dentro è chiamato automaticamente per noi.

L'ereditarietà delle classi segue lo stesso schema di Objective-C, un colon separa il nome della classe e quello della sua superclasse. Nell'esempio seguente, Hotel eredita dal BigHotel classe.

class BigHotel: Hotel 

Come in Objective-C, usiamo la notazione a punti per accedere alle proprietà di un oggetto. Tuttavia, Swift usa anche la notazione dot per invocare i metodi di classe e istanza come puoi vedere di seguito.

// Objective-C UIView * view = [[UIView alloc] init]; [self.view addSubview: view]; // Swift let view = UIView () self.view.addSubview (visualizza)

Proprietà

Un'altra differenza con Objective-C è che Swift non distingue tra variabili di istanza (ivars) e proprietà. Una variabile di istanza è una proprietà.

Dichiarare una proprietà è come definire una variabile o una costante, usando il var e permettere parole chiave. L'unica differenza è il contesto in cui sono definiti, cioè il contesto di una classe.

class Hotel let rooms = 10 var fullRooms = 0

Nell'esempio sopra, camere è un valore immutabile, una costante, impostata su 10 e fullRooms è una variabile con un valore iniziale di 0, che possiamo cambiare in seguito. La regola è che le proprietà devono essere inizializzate quando vengono dichiarate. L'unica eccezione a questa regola sono le opzioni, che discuteremo tra un momento.

Proprietà calcolate

Il linguaggio Swift definisce anche proprietà calcolate. Le proprietà calcolate non sono altro che getter e setter fantasiosi che non memorizzano alcun valore. Come indica il loro nome, sono calcolati o valutati al volo.

Di seguito è riportato un esempio di una proprietà calcolata. Ho cambiato il camere proprietà a a var per il resto di questi esempi. Scoprirai perché dopo.

class Hotel var rooms = 10 var fullRooms = 0 var description: String get return "Dimensione dell'hotel: \ (camere) capacità delle camere: \ (fullRooms) / \ (camere)"

Perché il descrizione la proprietà è di sola lettura e ha solo un ritorno dichiarazione, possiamo omettere il ottenere parola chiave e parentesi graffe, e mantenere solo il ritorno dichiarazione. Questa è una stenografia ed è quello che userò nel resto di questo tutorial.

class Hotel var rooms = 10 var fullRooms = 0 var description: String return "Dimensione dell'hotel: \ (camere) capacità delle camere: \ (fullRooms) / \ (rooms)"

Possiamo anche definire proprietà calcolate di lettura-scrittura. Nella nostra classe Hotel, noi vogliamo un emptyRooms proprietà che ottiene il numero di stanze vuote nell'hotel, ma vogliamo anche aggiornare fullRooms quando impostiamo il emptyRooms proprietà calcolata. Possiamo farlo usando il impostato parola chiave come mostrato di seguito.

class Hotel var rooms = 10 var fullRooms = 0 var description: String return "Dimensione dell'hotel: \ (camere) capacità delle camere: \ (fullRooms) / \ (rooms)" var emptyLe camere: Int get return rooms - fullRooms set // newValue constant è disponibile qui // contenente il valore passato if (newValue < rooms)  fullRooms = rooms - newValue  else  fullRooms = rooms     let h = Hotel() h.emptyRooms = 3 h.description // Size of Hotel: 10 rooms capacity:7/10

Nel emptyRooms setter, il newValue la costante ci viene consegnata e rappresenta il valore passato al setter. È anche importante notare che le proprietà calcolate sono sempre dichiarate come variabili, usando il var parola chiave, perché il loro valore calcolato può cambiare.

metodi

Abbiamo già trattato le funzioni in precedenza in questo articolo. I metodi non sono altro che funzioni legate a un tipo, ad esempio una classe. Nell'esempio seguente implementiamo un metodo di istanza, bookNumberOfRooms, nel Hotel classe che abbiamo creato in precedenza.

class Hotel var rooms = 10 var fullRooms = 0 var description: String return "Dimensione dell'hotel: \ (camere) capacità delle camere: \ (fullRooms) / \ (rooms)" var emptyLe camere: Int get return rooms - fullRooms set // newValue constant è disponibile qui // contenente il valore passato if (newValue < rooms)  fullRooms = rooms - newValue  else  fullRooms = rooms    func bookNumberOfRooms(room:Int = 1) -> Bool if (self.emptyRooms> room) self.fullRooms ++; return true else return false let h = Hotel () h.emptyRooms = 7 h.description // Dimensione dell'hotel: 10 camere capacità: 3/10 h.bookNumberOfRooms (room: 2) // restituisce true h .descrizione // Dimensioni dell'hotel: 10 camere capacità: 5/10 h.bookNumberOfRoom () // restituisce true h.description // Dimensione dell'hotel: 10 camere capacità: 6/10 

inizializzatori

L'inizializzatore predefinito per le classi è dentro. Nel dentro funzione, impostiamo i valori iniziali dell'istanza che viene creata.

Ad esempio, se avessimo bisogno di un Hotel sottoclasse con 100 stanze, quindi avremmo bisogno di un inizializzatore per impostare il camere proprietà a 100. Ricorda che in precedenza ho cambiato camere dall'essere una costante all'essere una variabile nel Hotel classe. Il motivo è che non possiamo cambiare le costanti ereditate in una sottoclasse, solo le variabili ereditate possono essere modificate.

classe BigHotel: Hotel init () super.init () rooms = 100 let bh = BigHotel () println (bh.description); // Dimensione dell'hotel: 100 camere capacità: 0/100

Gli inizializzatori possono anche prendere dei parametri. L'esempio seguente mostra come funziona.

class CustomHotel: Hotel init (size: Int) super.init () rooms = size let c = CustomHotel (size: 20) c.description // Dimensione dell'hotel: 20 camere capacità: 0/20

Metodi di sovrascrittura e proprietà calcolate

Questa è una delle cose più interessanti di Swift. In Swift, una sottoclasse può sovrascrivere sia i metodi che le proprietà calcolate. Per fare ciò, usiamo il oltrepassare parola chiave. Esaminiamo il descrizione proprietà calcolata nel CustomHotel classe:

class CustomHotel: Hotel init (size: Int) super.init () rooms = size override descrizione var: String return super.description + "Howdy!"  let c = CustomHotel (size: 20) c.description // Dimensioni dell'hotel: 20 camere capacità: 0/20 Howdy! 

Il risultato è quello descrizione restituisce il risultato della superclasse descrizione metodo con la stringa "Salve!" aggiunto ad esso.

Ciò che è interessante riguardo ai metodi di sovrascrittura e alle proprietà calcolate è il oltrepassare parola chiave. Quando il compilatore vede il oltrepassare parola chiave, verifica se la superclasse della classe implementa il metodo che viene sovrascritto. Il compilatore controlla anche se le proprietà e i metodi di una classe sono in conflitto con proprietà o metodi più in alto nella struttura di ereditarietà.

Non so quante volte un errore di battitura in un metodo sovrascritto in Objective-C mi ha fatto maledire, perché il codice non funzionava. In Swift, il compilatore ti dirà esattamente cosa c'è che non va in queste situazioni.

strutture

Strutture, definite con struct parola chiave, sono più potenti in Swift di quanto lo siano in C e Objective-C. In C, le strutture definiscono solo valori e puntatori. Le strutture di Swift sono come le strutture di C, ma supportano anche proprietà e metodi calcolati.

Qualunque cosa tu possa fare con una classe, puoi fare con una struttura, con due importanti differenze:

  • le strutture non supportano l'ereditarietà come fanno le classi
  • le strutture vengono passate per valore mentre le classi vengono passate per riferimento

Ecco alcuni esempi di strutture in Swift:

struct Rect var origine: Point var size: Dimensione var area: Double return size.width * size.height func isBiggerThanRect (r: Rect) -> Bool return (self.area> r.area) struct Point var x = 0 var y = 0 struct Size var width = 0 var height = 0

4. Opzionali

Soluzione a un problema

Gli optionals sono un nuovo concetto se vieni da Objective-C. Risolvono un problema che tutti dobbiamo affrontare come programmatori. Quando accediamo a una variabile di cui non siamo sicuri, di solito restituiamo un indicatore, noto come sentinella, per indicare che il valore restituito è un valore no. Lasciatemi illustrare questo con un esempio di Objective-C:

NSString * someString = @ "ABCDEF"; NSInteger pos = [someString rangeOfString: @ "B"]. Location; // pos = 1

Nell'esempio sopra, stiamo cercando di trovare la posizione di @ "B" nel someString. Se @ "B" viene trovato, la sua posizione o posizione è memorizzata in pos. Ma cosa succede se @ "B" non è stato trovato in someString?

La documentazione afferma che rangeOfString: restituisce un NSRange con Posizione impostato su NSNotFound costante. In caso di rangeOfString:, la sentinella è NSNotFound. Le sentinelle sono utilizzate per indicare che il valore restituito non è valido.

In Cocoa ci sono molti usi di questo concetto, ma il valore sentinella differisce dal contesto al contesto, 0, -1, NULLO, NSIntegerMax, INT_MAX, zero, ecc. Il problema per il programmatore è che lei deve ricordare quale sentinella viene usata in quale contesto. Se il programmatore non sta attento, può scambiare un valore valido per una sentinella e viceversa. Swift risolve questo problema con gli optionals. Per citare Brian Lanier "Optionals è l'unica sentinella a governarli tutti".

Gli optionals hanno due stati, a zero stato, il che significa che l'opzionale non contiene alcun valore e un secondo stato, il che significa che contiene un valore valido. Pensa agli optionals come a un pacchetto con un indicatore per dirti se il contenuto del pacchetto è valido o meno.

uso

Tutti i tipi in Swift possono diventare opzionali. Definiamo un opzionale aggiungendo a ? dopo la dichiarazione del tipo in questo modo:

lascia intuire: Int? // someInt == nil

Assegniamo un valore al pacchetto di un opzionale proprio come facciamo con costanti e variabili.

someInt = 10 // someInt! == 10

Ricorda che gli optionals sono come i pacchetti. Quando abbiamo dichiarato lascia intuire: Int?, abbiamo definito una casella vuota con un valore di zero. Assegnando il valore 10 all'opzionale, la scatola contiene un numero intero uguale a 10 e il suo indicatore o stato diventa non è nulla.

Per ottenere il contenuto di un opzionale usiamo il ! operatore. Dobbiamo essere sicuri che l'opzionale abbia un valore valido prima di scartarlo. In caso contrario, si verificherà un errore di runtime. Questo è il modo in cui accediamo al valore memorizzato in un opzionale:

if (someInt! = nil) println ("someInt: \ (someInt!)") else println ("someInt non ha valore") // someInt: 10

Lo schema sopra è così comune in Swift che possiamo semplificare il blocco di codice sopra usando vincolante facoltativo con il se lasciato parole chiave. Dai un'occhiata al blocco di codice aggiornato qui sotto.

if let value = someInt println ("someInt: \ (value)") else println ("someInt non ha valore")

Gli optionals sono l'unico tipo che può prendere un zero valore. Costanti e variabili non possono essere inizializzate o impostate su zero. Questo fa parte della politica di sicurezza di Swift, tutte le variabili e le costanti non opzionali dovere avere un valore.

5. Gestione della memoria

Se ricordi, quando è stato introdotto ARC, stavamo usando il forte e debole parole chiave per definire la proprietà dell'oggetto. Swift ha anche un forte e debole modello di proprietà, ma ne introduce anche uno nuovo, senza proprietario. Diamo un'occhiata al modello di proprietà di ciascun oggetto in Swift.

forte

I riferimenti forti sono di default in Swift. Il più delle volte, possediamo l'oggetto a cui stiamo facendo riferimento e siamo noi i responsabili della conservazione in vita dell'oggetto referenziato.

Poiché i riferimenti forti sono predefiniti, non è necessario mantenere esplicitamente un riferimento forte a un oggetto, ogni riferimento è un riferimento forte.

debole

Un riferimento debole in Swift indica che il riferimento punta a un oggetto che non siamo responsabili di mantenere in vita. Viene usato principalmente tra due oggetti che non hanno bisogno dell'altro per essere in giro in modo che l'oggetto possa continuare il suo ciclo di vita.

C'è uno ma, però. In Swift, i riferimenti deboli devono sempre essere variabili con un tipo facoltativo, perché sono impostati su zero quando l'oggetto di riferimento è deallocato. Il debole la parola chiave è usata per dichiarare una variabile come debole:

debole var view: UIView?

senza proprietario

I riferimenti noti sono nuovi per i programmatori Objective-C. Un riferimento sconosciuto significa che non siamo responsabili di mantenere vivo l'oggetto di riferimento, proprio come i riferimenti deboli.

La differenza con un riferimento debole è che un riferimento non assegnato non è impostato su zero quando l'oggetto a cui fa riferimento viene deallocato. Un'altra importante differenza con i riferimenti deboli è che i riferimenti non citati sono definiti come un tipo non opzionale.

I riferimenti noti possono essere costanti. Un oggetto sconosciuto non esiste senza il suo proprietario e quindi il riferimento non condiviso non è mai zero. I riferimenti noti hanno bisogno del senza proprietario parola chiave prima della definizione della variabile o della costante.

vista var indipendente: UIView

Conclusione

Swift è un linguaggio straordinario che ha un sacco di profondità e potenziale. È divertente scrivere programmi e rimuove molto del codice standard che scriviamo in Objective-C per assicurarci che il nostro codice sia sicuro.

Consiglio vivamente Swift Programming Language, che è disponibile gratuitamente su Apple iBooks Store.