Questa risposta è wiki della community . Se ritieni che potrebbe essere migliorato, sentiti libero di modificarlo !
Background: cos'è un optional?
In Swift, Optional
è un tipo generico che può contenere un valore (di qualsiasi tipo) o nessun valore.
In molti altri linguaggi di programmazione, un particolare valore "sentinella" viene spesso utilizzato per indicare la mancanza di un valore . In Objective-C, ad esempio, nil
(il puntatore null ) indica la mancanza di un oggetto. Ma questo diventa più complicato quando si lavora con tipi primitivi - dovrebbe -1
essere usato per indicare l'assenza di un numero intero, o forse INT_MIN
, o di qualche altro numero intero? Se si sceglie un valore particolare per indicare "nessun numero intero", ciò significa che non può più essere trattato come un valore valido .
Swift è un linguaggio sicuro, il che significa che la lingua ti aiuta a essere chiaro sui tipi di valori con cui il tuo codice può lavorare. Se parte del codice prevede una stringa, digitare safety impedisce di passargli un Int per errore.
In Swift, qualsiasi tipo può essere reso facoltativo . Un valore facoltativo può assumere qualsiasi valore dal tipo originale o dal valore speciale nil
.
Gli optionals sono definiti con un ?
suffisso sul tipo:
var anInt: Int = 42
var anOptionalInt: Int? = 42
var anotherOptionalInt: Int? // `nil` is the default when no value is provided
La mancanza di un valore in un facoltativo è indicata da nil
:
anOptionalInt = nil
(Nota che questo nil
non è lo stesso nil
di Objective-C. In Objective-C nil
è l'assenza di un puntatore oggetto valido ; in Swift, gli Optionals non sono limitati a oggetti / tipi di riferimento. Opzionale si comporta in modo simile a Forse di Haskell .)
Perché ho ricevuto " errore irreversibile: inaspettatamente trovato zero mentre scartavo un valore Opzionale "?
Per accedere a un valore opzionale (se ne ha uno), è necessario scartarlo . Un valore opzionale può essere scartato in modo sicuro o forzato. Se si scuote forzatamente un facoltativo e non aveva un valore, il programma si arresterà in modo anomalo con il messaggio sopra riportato.
Xcode ti mostrerà il crash evidenziando una riga di codice. Il problema si verifica su questa linea.
Questo arresto può verificarsi con due diversi tipi di riavvolgimento forzato:
1. Espulsione forzata della forza
Questo viene fatto con l' !
operatore su un optional. Per esempio:
let anOptionalString: String?
print(anOptionalString!) // <- CRASH
Errore irreversibile: trovato inaspettatamente zero durante lo scartamento di un valore facoltativo
Come anOptionalString
è nil
qui, si verificherà un arresto anomalo sulla linea in cui viene forzato scartarlo.
2. Optionals implicitamente non confezionati
Questi sono definiti con un !
, piuttosto che ?
dopo il tipo.
var optionalDouble: Double! // this value is implicitly unwrapped wherever it's used
Si presume che questi optionals contengano un valore. Pertanto, ogni volta che accedi a un accessorio implicitamente non scartato, questo verrà automaticamente forzato da scartare per te. Se non contiene un valore, si bloccherà.
print(optionalDouble) // <- CRASH
Errore irreversibile: trovato inaspettatamente zero mentre scartare implicitamente un valore Opzionale
Per capire quale variabile ha causato l'arresto anomalo, puoi tenere premuto ⌥mentre fai clic per mostrare la definizione, dove potresti trovare il tipo opzionale.
Gli IBOutlet, in particolare, sono di solito opzionali implicitamente da scartare. Questo perché il tuo xib o storyboard collegherà le prese in fase di esecuzione, dopo l' inizializzazione. Dovresti quindi assicurarti di non accedere agli outlet prima che vengano caricati. Dovresti anche verificare che le connessioni siano corrette nel tuo storyboard / file xib, altrimenti i valori saranno nil
in fase di esecuzione e quindi si arresterebbero in modo anomalo quando sono implicitamente non scartati . Quando si riparano le connessioni, provare a eliminare le righe di codice che definiscono le prese, quindi ricollegarle.
Quando dovrei mai forzare a scartare un Opzionale?
Espulsione forzata della forza
Come regola generale, non si dovrebbe mai forzare esplicitamente un involucro opzionale con l' !
operatore. Ci possono essere casi in cui l'utilizzo!
è accettabile, ma dovresti usarlo solo se sei sicuro al 100% che l'opzione contenga un valore.
Mentre ci può essere un'occasione in cui è possibile usare la forza scartare, come si sa per un fatto che un optional contiene un valore - non c'è un singolo posto in cui non si può tranquillamente scartare quella facoltativa, invece.
Optionals implicitamente non confezionati
Queste variabili sono progettate in modo da poter differire la loro assegnazione fino a quando nel tuo codice. E ' la vostra responsabilità di assicurare che abbiano un valore prima di accedere. Tuttavia, poiché comportano lo scartamento forzato, sono ancora intrinsecamente non sicuri, poiché presumono che il valore sia diverso da zero, anche se l'assegnazione nulla è valida.
Dovresti usare solo gli optional implicitamente non scartati come ultima risorsa . Se puoi usare una variabile pigra o fornire un valore predefinito per una variabile, dovresti farlo invece di utilizzare un facoltativo involontariamente non scartato.
Tuttavia, ci sono alcuni scenari in cui gli opzionali implicitamente non scartati sono utili e si è ancora in grado di utilizzare vari modi per scartarli in modo sicuro come elencato di seguito - ma è sempre necessario utilizzarli con la dovuta cautela.
Come posso gestire in sicurezza gli Optionals?
Il modo più semplice per verificare se un'opzione contiene un valore è confrontarlo nil
.
if anOptionalInt != nil {
print("Contains a value!")
} else {
print("Doesn’t contain a value.")
}
Tuttavia, il 99,9% delle volte quando si lavora con gli optionals, si vorrebbe effettivamente accedere al valore che contiene, se ne contiene uno. Per fare ciò, è possibile utilizzare il binding opzionale .
Rilegatura opzionale
Il binding facoltativo consente di verificare se un optional contiene un valore e consente di assegnare il valore non imballato a una nuova variabile o costante. Utilizza la sintassi if let x = anOptional {...}
o if var x = anOptional {...}
, a seconda se è necessario modificare il valore della nuova variabile dopo averla associata.
Per esempio:
if let number = anOptionalInt {
print("Contains a value! It is \(number)!")
} else {
print("Doesn’t contain a number")
}
Quello che fa è prima verificare che l'opzionale contenga un valore. Se si fa , allora il valore 'scartare' è assegnato a una nuova variabile ( number
) - che è quindi possibile utilizzare liberamente come se fosse non opzionale. Se l'opzione opzionale non contiene un valore, verrà invocata la clausola else, come previsto.
La cosa interessante di rilegatura opzionale è che puoi scartare più opzioni contemporaneamente. Puoi semplicemente separare le istruzioni con una virgola. La dichiarazione avrà esito positivo se tutti gli optionals non fossero scartati.
var anOptionalInt : Int?
var anOptionalString : String?
if let number = anOptionalInt, let text = anOptionalString {
print("anOptionalInt contains a value: \(number). And so does anOptionalString, it’s: \(text)")
} else {
print("One or more of the optionals don’t contain a value")
}
Un altro trucco è che puoi anche usare le virgole per verificare una determinata condizione sul valore, dopo averlo scartato.
if let number = anOptionalInt, number > 0 {
print("anOptionalInt contains a value: \(number), and it’s greater than zero!")
}
L'unico problema con l'utilizzo dell'associazione facoltativa all'interno di un'istruzione if è che è possibile accedere al valore non scartato solo all'interno dell'ambito dell'istruzione. Se è necessario accedere al valore al di fuori dell'ambito dell'istruzione, è possibile utilizzare un'istruzione guard .
Un'istruzione guard consente di definire una condizione per il successo e l'ambito corrente continuerà a essere eseguito solo se tale condizione è soddisfatta. Sono definiti con la sintassi guard condition else {...}
.
Quindi, per usarli con un'associazione opzionale, puoi farlo:
guard let number = anOptionalInt else {
return
}
(Si noti che all'interno del corpo di guardia, è necessario utilizzare una delle istruzioni di trasferimento di controllo per uscire dall'ambito del codice attualmente in esecuzione).
Se anOptionalInt
contiene un valore, verrà scartato e assegnato alla nuova number
costante. Il codice dopo la guardia continuerà quindi a essere eseguito. Se non contiene un valore, la guardia eseguirà il codice tra parentesi, il che porterà al trasferimento del controllo, in modo che il codice immediatamente successivo non venga eseguito.
La vera cosa bella delle dichiarazioni di guardia è che il valore non imballato è ora disponibile per l'uso nel codice che segue l'istruzione (come sappiamo che il codice futuro può essere eseguito solo se l'opzione ha un valore). Questo è un ottimo strumento per eliminare le "piramidi di sventura" create annidando più istruzioni if.
Per esempio:
guard let number = anOptionalInt else {
return
}
print("anOptionalInt contains a value, and it’s: \(number)!")
Le guardie supportano anche gli stessi trucchi che supporta l'istruzione if, come scartare più opzioni contemporaneamente e usare la where
clausola.
Il fatto che tu usi un'istruzione if o guard dipende completamente dal fatto che qualsiasi codice futuro richieda che facoltativo contenga un valore.
Operatore di coalescenza zero
La coalescenza Operatore Nil è una versione abbreviata del nifty operatore condizionale ternario , in primo luogo progettato per convertire optional per i non-optional. Ha la sintassi a ?? b
, dove a
è un tipo opzionale ed b
è dello stesso tipo di a
(sebbene di solito non facoltativo).
In sostanza ti consente di dire “Se a
contiene un valore, scartalo. Se invece non ritorna, b
invece ”. Ad esempio, potresti usarlo in questo modo:
let number = anOptionalInt ?? 0
Questo definirà una number
costante di Int
tipo, che conterrà il valore di anOptionalInt
, se contiene un valore, o 0
altrimenti.
È solo una scorciatoia per:
let number = anOptionalInt != nil ? anOptionalInt! : 0
Concatenamento opzionale
È possibile utilizzare il concatenamento opzionale per chiamare un metodo o accedere a una proprietà su un facoltativo. Questo è semplicemente fatto con il suffisso del nome della variabile con un ?
quando lo si utilizza.
Ad esempio, supponiamo di avere una variabile foo
, di tipo Foo
un'istanza facoltativa .
var foo : Foo?
Se volessimo chiamare un metodo foo
che non restituisce nulla, possiamo semplicemente fare:
foo?.doSomethingInteresting()
Se foo
contiene un valore, questo metodo verrà chiamato su di esso. In caso contrario, non accadrà nulla di negativo: il codice continuerà semplicemente a essere eseguito.
(Questo è un comportamento simile all'invio di messaggi nil
in Objective-C)
Questo può quindi essere utilizzato anche per impostare proprietà e metodi di chiamata. Per esempio:
foo?.bar = Bar()
Anche in questo caso, nulla di male accadrà qui se foo
è nil
. Il tuo codice continuerà semplicemente a essere eseguito.
Un altro trucco utile che il concatenamento opzionale consente di verificare è se l'impostazione di una proprietà o la chiamata di un metodo hanno avuto esito positivo. È possibile farlo confrontando il valore restituito con nil
.
(Questo perché verrà restituito un valore facoltativo Void?
anziché Void
su un metodo che non restituisce nulla)
Per esempio:
if (foo?.bar = Bar()) != nil {
print("bar was set successfully")
} else {
print("bar wasn’t set successfully")
}
Tuttavia, le cose diventano un po 'più difficili quando si tenta di accedere a proprietà o chiamare metodi che restituiscono un valore. Poiché foo
è facoltativo, tutto ciò che viene restituito sarà facoltativo. Per far fronte a questo, è possibile scartare gli optionals che vengono restituiti utilizzando uno dei metodi sopra indicati oppure scartare foo
se stessi prima di accedere ai metodi o chiamare metodi che restituiscono valori.
Inoltre, come suggerisce il nome, è possibile "concatenare" queste dichiarazioni insieme. Ciò significa che se foo
ha una proprietà opzionale baz
, che ha una proprietà qux
, è possibile scrivere quanto segue:
let optionalQux = foo?.baz?.qux
Ancora una volta, poiché foo
e baz
sono facoltativi, il valore restituito da qux
sarà sempre facoltativo, indipendentemente dal fatto che qux
sia facoltativo.
map
e flatMap
Una caratteristica spesso sottoutilizzata con gli optional è la capacità di usare le funzioni map
e flatMap
. Ciò consente di applicare trasformazioni non opzionali a variabili opzionali. Se un'opzione ha un valore, è possibile applicare una determinata trasformazione ad essa. Se non ha un valore, rimarrà nil
.
Ad esempio, supponiamo che tu abbia una stringa opzionale:
let anOptionalString:String?
Applicando la map
funzione ad essa - possiamo usare la stringByAppendingString
funzione per concatenarla a un'altra stringa.
Poiché stringByAppendingString
accetta un argomento stringa non facoltativo, non possiamo inserire direttamente la nostra stringa opzionale. Tuttavia, usando map
, possiamo usare consentire stringByAppendingString
di essere usato se anOptionalString
ha un valore.
Per esempio:
var anOptionalString:String? = "bar"
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // Optional("foobar")
Tuttavia, se anOptionalString
non ha un valore, map
verrà restituito nil
. Per esempio:
var anOptionalString:String?
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // nil
flatMap
funziona in modo simile a map
, tranne per il fatto che consente di restituire un altro optional all'interno del corpo di chiusura. Ciò significa che è possibile inserire un opzionale in un processo che richiede un input non facoltativo, ma che può generare un opzionale stesso.
try!
Il sistema di gestione degli errori di Swift può essere tranquillamente utilizzato con Do-Try-Catch :
do {
let result = try someThrowingFunc()
} catch {
print(error)
}
Se someThrowingFunc()
genera un errore, l'errore verrà catturato in modo sicuro nel catch
blocco.
Il error
costante che vedi nel catch
blocco non è stata dichiarata da noi - è generata automaticamente da catch
.
Puoi anche dichiarare error
, ha il vantaggio di poterlo trasmettere in un formato utile, ad esempio:
do {
let result = try someThrowingFunc()
} catch let error as NSError {
print(error.debugDescription)
}
L'utilizzo in try
questo modo è il modo corretto di provare, rilevare e gestire gli errori derivanti dalle funzioni di lancio.
C'è anche try?
che assorbe l'errore:
if let result = try? someThrowingFunc() {
// cool
} else {
// handle the failure, but there's no error information available
}
Ma il sistema di gestione degli errori di Swift fornisce anche un modo per "forzare provare" con try!
:
let result = try! someThrowingFunc()
I concetti spiegati in questo post si applicano anche qui: se viene generato un errore, l'applicazione si arresterà in modo anomalo.
Si dovrebbe usare sempre e solo try!
se puoi provare che il suo risultato non fallirà mai nel tuo contesto - e questo è molto raro.
Il più delle volte utilizzerai il sistema Do-Try-Catch completo e quello opzionale try?
, nei rari casi in cui la gestione dell'errore non è importante.
risorse