Qual è un valore opzionale in Swift?


267

Dalla documentazione di Apple :

È possibile utilizzare ife letinsieme per lavorare con valori che potrebbero mancare. Questi valori sono rappresentati come opzionali. Un valore facoltativo contiene un valore o contiene nilper indicare che il valore è mancante. Scrivi un punto interrogativo ( ?) dopo il tipo di un valore per contrassegnare il valore come facoltativo.

Perché vorresti utilizzare un valore opzionale?



1
Opzionale può anche essere visto come un'implementazione della monade Opzione / Forse . Questo blog qui fa un buon lavoro nel cercare di spiegare ciò che altrimenti sarebbe un concetto difficile.
StuartLC,

tldr: "Swift ha bisogno che tu sia chiaro quando un valore può mancare e quando è garantito che esista." dall'eccellente risposta
jonatan

Risposte:


532

Un optional in Swift è un tipo che può contenere un valore o nessun valore. Gli optionals vengono scritti aggiungendo a ?a qualsiasi tipo:

var name: String? = "Bertie"

Gli optionals (insieme a Generics) sono uno dei concetti Swift più difficili da comprendere. A causa del modo in cui sono scritti e utilizzati, è facile farsi un'idea sbagliata di quello che sono. Confronta il sopra opzionale con la creazione di una normale stringa:

var name: String = "Bertie" // No "?" after String

Dalla sintassi sembra che una stringa opzionale sia molto simile a una normale stringa. Non è. Una stringa opzionale non è una stringa con alcune impostazioni "opzionali" attivate. Non è una varietà speciale di String. Una stringa e una stringa opzionale sono tipi completamente diversi.

Ecco la cosa più importante da sapere: un optional è un tipo di contenitore. Una stringa opzionale è un contenitore che potrebbe contenere una stringa. Un Int opzionale è un contenitore che potrebbe contenere un Int. Pensa a un optional come a una specie di pacco. Prima di aprirlo (o "scartarlo" nella lingua degli optionals) non saprai se contiene qualcosa o niente.

Puoi vedere come gli optional sono implementati nella libreria standard di Swift digitando "Opzionale" in qualsiasi file Swift e facendo clic su di esso. Ecco la parte importante della definizione:

enum Optional<Wrapped> {
    case none
    case some(Wrapped)
}

Opzionale è solo uno enumche può essere uno dei due casi: .noneo .some. Se lo è .some, c'è un valore associato che, nell'esempio sopra, sarebbe String"Hello". Un facoltativo utilizza Generics per assegnare un tipo al valore associato. Il tipo di una stringa opzionale non è String, è Optionalo più precisamente Optional<String>.

Tutto ciò che Swift fa con gli optionals è magico per rendere più fluida la lettura e la scrittura del codice. Purtroppo questo oscura il modo in cui funziona davvero. Esaminerò alcuni dei trucchi più tardi.

Nota: parlerò molto delle variabili opzionali, ma va bene anche creare costanti opzionali. Contrassegno tutte le variabili con il loro tipo per semplificare la comprensione dei tipi di tipo creati, ma non è necessario nel proprio codice.


Come creare optionals

Per creare un facoltativo, aggiungi un ?dopo il tipo che desideri avvolgere. Qualsiasi tipo può essere facoltativo, anche i tuoi tipi personalizzati. Non puoi avere uno spazio tra il tipo e il ?.

var name: String? = "Bob" // Create an optional String that contains "Bob"
var peter: Person? = Person() // An optional "Person" (custom type)

// A class with a String and an optional String property
class Car {
var modelName: String // must exist
var internalName: String? // may or may not exist
}

Utilizzando gli opzionali

Puoi confrontare un facoltativo nilper vedere se ha un valore:

var name: String? = "Bob"
name = nil // Set name to nil, the absence of a value
if name != nil {
    print("There is a name")
}
if name == nil { // Could also use an "else"
    print("Name has no value")
}

Questo è un po 'confuso. Implica che un optional è una cosa o l'altra. È zero o è "Bob". Questo non è vero, l'opzione non si trasforma in qualcos'altro. Confrontarlo con zero è un trucco per rendere il codice più facile da leggere. Se un opzionale è uguale a zero, significa che l'enum è attualmente impostato su .none.


Solo gli opzionali possono essere nulli

Se si tenta di impostare una variabile non facoltativa su zero, verrà visualizzato un errore.

var red: String = "Red"
red = nil // error: nil cannot be assigned to type 'String'

Un altro modo di vedere gli opzionali è un complemento alle normali variabili Swift. Sono una controparte di una variabile che ha un valore garantito. Swift è un linguaggio attento che odia l'ambiguità. La maggior parte delle variabili sono definite come non opzionali, ma a volte ciò non è possibile. Ad esempio, immagina un controller di visualizzazione che carica un'immagine da una cache o dalla rete. Potrebbe avere o meno quell'immagine al momento della creazione del controller di visualizzazione. Non è possibile garantire il valore per la variabile immagine. In questo caso dovresti renderlo facoltativo. Inizia come nile quando l'immagine viene recuperata, l'opzione opzionale ottiene un valore.

L'uso di un optional rivela l'intenzione dei programmatori. Rispetto a Objective-C, dove qualsiasi oggetto potrebbe essere nullo, Swift ha bisogno che tu sia chiaro quando un valore può mancare e quando è garantito che esista.


Per usare un optional, devi "scartarlo"

Un opzionale Stringnon può essere utilizzato al posto di un effettivo String. Per utilizzare il valore spostato all'interno di un facoltativo, è necessario scartarlo. Il modo più semplice per scartare un optional è aggiungere un !dopo il nome opzionale. Questo si chiama "forzatura da scartare". Restituisce il valore all'interno dell'opzione (come il tipo originale) ma se l'opzione è nil, provoca un arresto anomalo del runtime. Prima di scartare, dovresti essere sicuro che ci sia un valore.

var name: String? = "Bob"
let unwrappedName: String = name!
print("Unwrapped name: \(unwrappedName)")

name = nil
let nilName: String = name! // Runtime crash. Unexpected nil.

Verifica e utilizzo di un opzionale

Poiché è sempre necessario verificare la presenza di zero prima di scartare e utilizzare un opzionale, questo è un modello comune:

var mealPreference: String? = "Vegetarian"
if mealPreference != nil {
    let unwrappedMealPreference: String = mealPreference!
    print("Meal: \(unwrappedMealPreference)") // or do something useful
}

In questo modello si controlla che sia presente un valore, quindi quando si è sicuri che lo si sia, si impone di scartarlo in una costante temporanea da utilizzare. Poiché questa è una cosa così comune da fare, Swift offre una scorciatoia usando "if let". Questo si chiama "associazione opzionale".

var mealPreference: String? = "Vegetarian"
if let unwrappedMealPreference: String = mealPreference {
    print("Meal: \(unwrappedMealPreference)") 
}

Questo crea una costante temporanea (o variabile se si sostituisce letcon var) il cui ambito è compreso solo tra parentesi graffe if. Poiché dover usare un nome come "unwrappedMealPreference" o "realMealPreference" è un onere, Swift ti consente di riutilizzare il nome della variabile originale, creando un nome temporaneo nell'ambito del bracketing

var mealPreference: String? = "Vegetarian"
if let mealPreference: String = mealPreference {
    print("Meal: \(mealPreference)") // separate from the other mealPreference
}

Ecco un po 'di codice per dimostrare che viene utilizzata una variabile diversa:

var mealPreference: String? = "Vegetarian"
if var mealPreference: String = mealPreference {
    print("Meal: \(mealPreference)") // mealPreference is a String, not a String?
    mealPreference = "Beef" // No effect on original
}
// This is the original mealPreference
print("Meal: \(mealPreference)") // Prints "Meal: Optional("Vegetarian")"

L'associazione opzionale funziona verificando se l'opzione è uguale a zero. In caso contrario, sposta il componente opzionale nella costante fornita ed esegue il blocco. In Xcode 8.3 e versioni successive (Swift 3.1), il tentativo di stampare un opzionale come questo provocherà un avviso inutile. Usa gli optional debugDescriptionper silenziarlo:

print("\(mealPreference.debugDescription)")

A cosa servono gli optionals?

Gli optionals hanno due casi d'uso:

  1. Cose che possono fallire (mi aspettavo qualcosa ma non ho ottenuto nulla)
  2. Cose che non sono niente ora ma potrebbero essere qualcosa in seguito (e viceversa)

Alcuni esempi concreti:

  • Una proprietà che può essere lì o non lì, come middleNameo spousein una Personclasse
  • Un metodo che può restituire un valore o nulla, come la ricerca di una corrispondenza in un array
  • Un metodo che può restituire un risultato o ottenere un errore e non restituire nulla, come tentare di leggere il contenuto di un file (che normalmente restituisce i dati del file) ma il file non esiste
  • Proprietà delegate, che non devono sempre essere impostate e generalmente vengono impostate dopo l'inizializzazione
  • Per le weakproprietà nelle classi. La cosa a cui indicano può essere impostata nilin qualsiasi momento
  • Una grande risorsa che potrebbe essere necessario rilasciare per recuperare la memoria
  • Quando è necessario un modo per sapere quando è stato impostato un valore (dati non ancora caricati> i dati) anziché utilizzare un dato separato Boolean

Gli optionals non esistono in Objective-C ma esiste un concetto equivalente, che restituisce zero. I metodi che possono restituire un oggetto possono invece restituire zero. Questo significa "l'assenza di un oggetto valido" e viene spesso usato per dire che qualcosa è andato storto. Funziona solo con oggetti Objective-C, non con primitive o tipi C di base (enumerazioni, strutture). Objective-C aveva spesso tipi specializzati per rappresentare l'assenza di questi valori ( NSNotFoundche è in realtà NSIntegerMax, kCLLocationCoordinate2DInvalidper rappresentare una coordinata non valida, -1oppure vengono utilizzati anche alcuni valori negativi). Il programmatore deve conoscere questi valori speciali, quindi devono essere documentati e appresi per ogni caso. Se un metodo non può assumere nilcome parametro, questo deve essere documentato. In Objective-C,nilera un puntatore proprio come tutti gli oggetti erano definiti come puntatori, ma nilpuntava a uno specifico indirizzo (zero). In Swift, nilè un letterale che significa l'assenza di un certo tipo.


Rispetto a nil

Prima eri in grado di utilizzare qualsiasi opzione come Boolean:

let leatherTrim: CarExtras? = nil
if leatherTrim {
    price = price + 1000
}

Nelle versioni più recenti di Swift devi usare leatherTrim != nil. Perchè è questo? Il problema è che a Booleanpuò essere racchiuso in un facoltativo. Se hai Booleancosì:

var ambiguous: Boolean? = false

ha due tipi di "falso", uno in cui non esiste alcun valore e uno in cui ha un valore ma il valore è false. Swift odia l'ambiguità, quindi ora devi sempre controllare un opzionale contro nil.

Potresti chiederti qual è il punto di un optional Boolean? Come con altri optionals, lo .nonestato potrebbe indicare che il valore è ancora sconosciuto. Potrebbe esserci qualcosa all'altra estremità di una chiamata di rete che richiede del tempo per il polling. I booleani opzionali sono anche chiamati " booleani a tre valori "


Trucchi veloci

Swift usa alcuni trucchi per consentire agli optional di funzionare. Considera queste tre righe di codice opzionale dall'aspetto ordinario;

var religiousAffiliation: String? = "Rastafarian"
religiousAffiliation = nil
if religiousAffiliation != nil { ... }

Nessuna di queste righe dovrebbe essere compilata.

  • La prima riga imposta una stringa opzionale usando un valore letterale di stringa, due tipi diversi. Anche se questo era un Stringi tipi sono diversi
  • La seconda riga imposta una stringa opzionale su zero, due tipi diversi
  • La terza riga confronta una stringa opzionale con zero, due tipi diversi

Esaminerò alcuni dettagli di implementazione degli optionals che consentono a queste linee di funzionare.


Creare un opzionale

L'utilizzo ?per creare un facoltativo è zucchero sintattico, abilitato dal compilatore Swift. Se vuoi farlo a lungo, puoi creare un optional come questo:

var name: Optional<String> = Optional("Bob")

Questo chiama Optionalil primo inizializzatore, public init(_ some: Wrapped)che deduce il tipo associato all'opzione dal tipo usato tra parentesi.

Il modo ancora più lungo di creare e impostare un optional:

var serialNumber:String? = Optional.none
serialNumber = Optional.some("1234")
print("\(serialNumber.debugDescription)")

Impostazione di un facoltativo su nil

È possibile creare un facoltativo senza valore iniziale o crearne uno con il valore iniziale di nil(entrambi hanno lo stesso risultato).

var name: String?
var name: String? = nil

Consentire agli opzionali di eguagliare nilè abilitato dal protocollo ExpressibleByNilLiteral(precedentemente denominato NilLiteralConvertible). L'opzione viene creato con Optionalla seconda initializer 's, public init(nilLiteral: ()). I documenti dicono che non dovresti usare ExpressibleByNilLiteralnulla tranne gli opzionali, poiché ciò cambierebbe il significato di zero nel tuo codice, ma è possibile farlo:

class Clint: ExpressibleByNilLiteral {
    var name: String?
    required init(nilLiteral: ()) {
        name = "The Man with No Name"
    }
}

let clint: Clint = nil // Would normally give an error
print("\(clint.name)")

Lo stesso protocollo consente di impostare un facoltativo già creato su nil. Sebbene non sia raccomandato, è possibile utilizzare direttamente l'inizializzatore zero letteral:

var name: Optional<String> = Optional(nilLiteral: ())

Confrontando un facoltativo con nil

Gli optionals definiscono due operatori "==" e "! =" Speciali, che puoi vedere nella Optionaldefinizione. Il primo ==consente di verificare se qualche opzione è uguale a zero. Due diversi optionals impostati su .none saranno sempre uguali se i tipi associati sono uguali. Quando si confronta con zero, dietro le quinte Swift crea un optional dello stesso tipo associato, impostato su .none quindi lo utilizza per il confronto.

// How Swift actually compares to nil
var tuxedoRequired: String? = nil
let temp: Optional<String> = Optional.none
if tuxedoRequired == temp { // equivalent to if tuxedoRequired == nil
    print("tuxedoRequired is nil")
}

Il secondo ==operatore consente di confrontare due opzioni. Entrambi devono essere dello stesso tipo e quel tipo deve essere conforme Equatable(il protocollo che consente di confrontare le cose con il normale operatore "=="). Swift (presumibilmente) scartare i due valori e li confronta direttamente. Gestisce anche il caso in cui si trovano uno o entrambi gli optional .none. Nota la distinzione tra confronto con il nilletterale.

Inoltre, ti consente di confrontare qualsiasi Equatabletipo con un involucro opzionale quel tipo:

let numberToFind: Int = 23
let numberFromString: Int? = Int("23") // Optional(23)
if numberToFind == numberFromString {
    print("It's a match!") // Prints "It's a match!"
}

Dietro le quinte, Swift avvolge il non opzionale come opzionale prima del confronto. Funziona anche con i letterali ( if 23 == numberFromString {)

Ho detto che ci sono due ==operatori, ma in realtà ce n'è un terzo che ti consente di mettere nilsul lato sinistro del confronto

if nil == name { ... }

Optionals di denominazione

Non esiste una convenzione Swift per la denominazione dei tipi opzionali in modo diverso dai tipi non opzionali. La gente evita di aggiungere qualcosa al nome per mostrare che è un facoltativo (come "optionalMiddleName" o "possibleNumberAsString") e lascia che la dichiarazione mostri che è un tipo opzionale. Questo diventa difficile quando vuoi nominare qualcosa per contenere il valore da un facoltativo. Il nome "middleName" implica che si tratta di un tipo String, quindi quando si estrae il valore String da esso, spesso si può finire con nomi come "actualMiddleName" o "unwrappedMiddleName" o "realMiddleName". Utilizzare l'associazione opzionale e riutilizzare il nome della variabile per aggirare questo.


La definizione ufficiale

Da "The Basics" nel linguaggio di programmazione Swift :

Swift introduce anche tipi opzionali, che gestiscono l'assenza di un valore. Gli opzionali dicono "c'è un valore ed equivale a x" o "non c'è affatto un valore". Gli optionals sono simili all'utilizzo di zero con puntatori in Objective-C, ma funzionano per qualsiasi tipo, non solo per le classi. Gli optionals sono più sicuri ed espressivi dei puntatori zero in Objective-C e sono al centro di molte delle funzionalità più potenti di Swift.

Gli optionals sono un esempio del fatto che Swift è un linguaggio sicuro. Swift ti aiuta a chiarire i tipi di valori con cui il tuo codice può funzionare. Se parte del codice prevede una stringa, digitare safety impedisce di passargli un Int per errore. Ciò consente di rilevare e correggere gli errori il più presto possibile nel processo di sviluppo.


Per finire, ecco una poesia del 1899 sugli opzionali:

Ieri sulle scale
ho incontrato un uomo che non era lì
Non era di nuovo lì oggi
Vorrei, vorrei che se ne andasse

Antigonish


Altre risorse:


5
@KaanDedeoglu Purtroppo Steve è davvero un optional. Era qui e ora non lo è.
re nevan,

19
if myStringnon compila più. Hai bisogno if myString != nil. Vedere la documentazione .
Pang

5
spiegazione migliore e più chiara per? e ! uso in Swift che ho trovato sul web. grazie
mindbomb

2
mateo spiega in dettaglio gli Optionals, raggiungendo in profondità e semplici esempi.
iluvatar_GR,

4
Grazie per questa spiegazione, è molto più chiaro della documentazione di Apple.
yesthisisjoe,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.