Il caso 1 e il caso 2 sono identici - sono entrambi assegnazioni del contenuto di a
a una nuova variabile. L'unica differenza è che stai lasciando Swift per dedurre il tipo di unwrapped_a
nell'opzione 1, mentre stai dando manualmente l'opzione type 2. Il motivo principale per cui dovresti fare l'opzione 2 è se il valore di origine fosse ambiguo - ad esempio se si trattasse di una funzione sovraccaricata che potrebbe restituire più tipi.
Il caso 3 è piuttosto interessante.
Ogni volta che si ha un valore, Swift sarà sempre disposto ad aggiornarlo silenziosamente a un valore di avvolgimento facoltativo, se aiuta a rendere i tipi corrispondenti e il codice compilato. L'aggiornamento automatico rapido dei tipi è abbastanza raro (non aggiornerà implicitamente un Int16
ad un Int32
per esempio) ma i valori agli optionals sono un'eccezione.
Ciò significa che è possibile passare i valori ovunque è necessario un optional, senza dover preoccuparsi di avvolgerla:
func f(maybe: Int?) { ... }
let i = 1
// you can just pass a straight value:
f(i)
// Swift will turn this into this:
f(Optional(i))
Quindi nel tuo ultimo esempio, hai detto Swift si desidera not_unwrapped_a
di essere un Int?
.Ma fa parte di un let
che richiede a
da scartare prima che sia assegnato ad esso.
Presentato con questo, l'unico modo in cui Swift può farlo funzionare è implicitamente avvolgere a
in un altro opzionale, quindi è quello che fa. Ora è un opzionale contenente un opzionale contenente nil. Questo non è un optional a valore nullo - è un optional che contiene un valore (di un opzionale contenente nil). Unwrapping che ti dà un opzionale contenente nil. È sembra come non è successo niente. Ma lo ha fatto - è stato incartato una seconda volta, poi scartato una volta.
Si può vedere questo in azione se si compila il codice di esempio con swiftc -dump-ast source.swift
. Vedrai la frase inject_into_optional implicit type='Int??’
. Il Int??
è un opzionale contenente un opzionale.
Gli optionals contenenti opzionali non sono casi limite oscuri: possono accadere facilmente. Per esempio, se mai per ... in un array contenente opzioni, o usato un pedice per ottenere un valore da un dizionario che conteneva opzioni, allora gli optionals degli optionals sono stati coinvolti in quel processo.
Un altro modo di pensare a questo è se si pensa di if let x = y { }
come una specie di * come una funzione, if_let
, definiti come segue:
func if_let<T>(optVal: T?, block: T->()) {
if optVal != nil {
block(optVal!)
}
}
Ora immaginate se avete fornito un block
che ha preso un Int?
- che è, T
sarebbe un Int?
. Quindi T?
sarebbe un Int??
. Quando hai passato un normale Int?
nello stesso if_let
insieme a quel blocco, Swift lo aggiornava implicitamente su un Int??
per farlo compilare. Questo è essenzialmente ciò che sta accadendo con lo if let not_unwrapped_a:Int?
.
Sono d'accordo, l'aggiornamento opzionale implicito a volte può essere sorprendente (ancora più sorprendente è che Swift aggiornerà le funzioni che restituiscono optionals, vale a dire se una funzione prende un (Int)->Int?
, aggiornerà un (Int)->Int
per restituire un opzionale). Ma presumibilmente il sentimento è la confusione potenziale ne vale la pena per la convenienza in questo caso.
* solo tipo di
Echoing Antonio di seguito, l'ultimo caso mi sembra ridondante e non necessario. Lo scopo di "se let' è una scorciatoia per scartare gli optionals e solo eseguirlo quando non è nulla. Il terzo caso dice "se questo valore nil/non-nil è nullo o non-zero (ovviamente lo è), esegui il blocco indipendentemente", che è ciò che accade. È in pratica lo stesso che usare una normale istruzione 'if', ignorando la funzione di sicurezza' if let' fornisce quando fallisce con garbo su valori nulli. Dovresti comunque gestire nuovamente i valori 'nil' all'interno del blocco, il che vanifica lo scopo di entrambi gli optionals e il binding facoltativo. – mc01