Perché creare "Optionals implicitamente non confezionati", poiché ciò implica che sai che esiste un valore?


493

Perché dovresti creare un "Opzionalmente implicitamente non confezionato" vs creare solo una variabile o costante regolare? Se sai che può essere scartato con successo, allora perché creare un optional in primo luogo? Ad esempio, perché è questo:

let someString: String! = "this is the string"

sarà più utile di:

let someString: String = "this is the string"

Se "gli opzionali indicano che una costante o variabile può avere" nessun valore "", ma "a volte è chiaro dalla struttura di un programma che un opzionale avrà sempre un valore dopo che quel valore è stato impostato per la prima volta", qual è il punto di rendendolo un optional in primo luogo? Se sai che un opzionale avrà sempre un valore, non lo rende non opzionale?

Risposte:


127

Si consideri il caso di un oggetto che può avere proprietà pari a zero mentre viene costruito e configurato, ma è immutabile e non nulla in seguito (NSImage viene spesso trattato in questo modo, sebbene nel suo caso sia comunque utile mutare a volte). Gli optionals implicitamente non imballati ripulirebbero molto il suo codice, con una perdita di sicurezza relativamente bassa (fintanto che una sola garanzia fosse mantenuta, sarebbe sicura).

(Modifica) Per essere chiari: gli opzionali regolari sono quasi sempre preferibili.


459

Prima di poter descrivere i casi d'uso di Optionals implicitamente non confezionati, dovresti già capire quali sono gli Optionals e gli Optional implicitamente non confezionati in Swift. In caso contrario, ti consiglio di leggere prima il mio articolo sugli optionals

Quando utilizzare un facoltativo involontariamente non confezionato

Ci sono due ragioni principali per cui si potrebbe creare un Opzionale implicitamente non confezionato. Tutti hanno a che fare con la definizione di una variabile a cui non si accederà mai nilperché altrimenti, il compilatore Swift ti costringerà sempre a scartare esplicitamente un Opzionale.

1. Una costante che non può essere definita durante l'inizializzazione

Ogni costante membro deve avere un valore al termine dell'inizializzazione. A volte, una costante non può essere inizializzata con il suo valore corretto durante l'inizializzazione, ma può comunque essere garantita la presenza di un valore prima dell'accesso.

L'uso di una variabile opzionale risolve questo problema perché un opzionale viene inizializzato automaticamente con nile il valore che conterrà eventualmente rimarrà immutabile. Tuttavia, può essere una seccatura scartare costantemente una variabile che sicuramente non è nulla. Gli Optionals implicitamente non confezionati ottengono gli stessi vantaggi di un Optional con l'ulteriore vantaggio di non doverlo scartare esplicitamente ovunque.

Un ottimo esempio di ciò è quando una variabile membro non può essere inizializzata in una sottoclasse UIView fino a quando la vista non viene caricata:

class MyView: UIView {
    @IBOutlet var button: UIButton!
    var buttonOriginalWidth: CGFloat!

    override func awakeFromNib() {
        self.buttonOriginalWidth = self.button.frame.size.width
    }
}

Qui, non puoi calcolare la larghezza originale del pulsante fino a quando la vista non viene caricata, ma sai che awakeFromNibverrà chiamato prima di qualsiasi altro metodo sulla vista (diverso dall'inizializzazione). Invece di forzare il valore in modo esplicito da scartare inutilmente in tutta la classe, è possibile dichiararlo come Opzionale implicitamente non impacchettato.

2. Quando l'app non può recuperare da un essere variabile nil

Questo dovrebbe essere estremamente raro, ma se la tua app non può continuare a funzionare se si nilaccede a una variabile , sarebbe una perdita di tempo preoccuparsi di provarla nil. Normalmente se hai una condizione che deve essere assolutamente vera affinché la tua app continui a funzionare, useresti un assert. Un Opzionalmente implicitamente non confezionato ha un'affermazione per zero incorporato proprio in esso. Anche in questo caso, è spesso utile scartare il componente opzionale e utilizzare un'asserzione più descrittiva se è nulla.

Quando non usare un optional implicitamente non confezionato

1. Variabili membro calcolate in modo pigramente

A volte hai una variabile membro che non dovrebbe mai essere nulla, ma non può essere impostata sul valore corretto durante l'inizializzazione. Una soluzione consiste nell'utilizzare un Opzionale implicitamente non avvolto, ma un modo migliore è usare una variabile pigra:

class FileSystemItem {
}

class Directory : FileSystemItem {
    lazy var contents : [FileSystemItem] = {
        var loadedContents = [FileSystemItem]()
        // load contents and append to loadedContents
        return loadedContents
    }()
}

Ora, la variabile membro contentsnon è inizializzata fino al primo accesso. Ciò dà alla classe la possibilità di entrare nello stato corretto prima di calcolare il valore iniziale.

Nota: questo può sembrare contraddire il numero 1 dall'alto. Tuttavia, è importante fare una distinzione. Quanto buttonOriginalWidthsopra deve essere impostato durante viewDidLoad per impedire a chiunque di modificare la larghezza dei pulsanti prima di accedere alla proprietà.

2. Ovunque altro

Per la maggior parte, gli Optionals implicitamente non confezionati dovrebbero essere evitati perché se usati in modo errato, l'intera app andrà in crash quando si accede durante nil. Se non sei mai sicuro che una variabile possa essere nulla, usa sempre un normale Opzionale. Disimballare una variabile che non è mai nilcerto non fa molto male.


4
Questa risposta dovrebbe essere aggiornata per la beta 5. Non è più possibile utilizzare if someOptional.
Babbo Natale,

2
@SantaClaus hasValueè definito direttamente su Opzionale. Preferisco la semantica di hasValuequella di != nil. Penso che sia molto più comprensibile per i nuovi programmatori che non hanno usato nilin altre lingue. hasValueè molto più logico di nil.
Drewag,

2
Sembra che sia hasValuestato estratto dalla beta 6. Ash lo ha reinserito però ... github.com/AshFurrow/hasValue
Chris Wagner,

1
@newacct Per quanto riguarda il tipo di ritorno degli inizializzatori Objc, è più che altro un implicito Implicly Unwrapped Optional. Il comportamento che hai descritto per l'utilizzo di un "non opzionale" è esattamente quello che farebbe un Opzionale implicitamente non avvolto (non fallire fino a quando non ti si accede). Per quanto riguarda il lasciare che il programma fallisca in precedenza scartandolo con la forza, sono d'accordo che è preferibile utilizzare un non opzionale, ma ciò non è sempre possibile.
Drewag,

1
@confile no. Indipendentemente da ciò, apparirà in Objective-C come un puntatore (se fosse facoltativo, implicitamente non scartato o non opzionale).
Drewag,

56

Gli optionals implicitamente non scartati sono utili per presentare una proprietà come non opzionale quando in realtà deve essere opzionale sotto le coperte. Ciò è spesso necessario per "legare il nodo" tra due oggetti correlati che hanno bisogno di un riferimento all'altro. Ha senso quando nessuno dei due riferimenti è effettivamente facoltativo, ma uno di essi deve essere nullo durante l'inizializzazione della coppia.

Per esempio:

// These classes are buddies that never go anywhere without each other
class B {
    var name : String
    weak var myBuddyA : A!
    init(name : String) {
        self.name = name
    }
}

class A {
    var name : String
    var myBuddyB : B
    init(name : String) {
        self.name = name
        myBuddyB = B(name:"\(name)'s buddy B")
        myBuddyB.myBuddyA = self
    }
}

var a = A(name:"Big A")
println(a.myBuddyB.name)   // prints "Big A's buddy B"

Ogni Bistanza dovrebbe sempre avere un myBuddyAriferimento valido , quindi non vogliamo che l'utente lo consideri facoltativo, ma è necessario che sia facoltativo in modo da poter costruire un Bprima di Apoter fare riferimento.

PERÒ! Questo tipo di requisiti di riferimento reciproco è spesso un'indicazione di accoppiamento stretto e design scadente. Se ti ritrovi a fare affidamento su optionals implicitamente da scartare, dovresti probabilmente considerare il refactoring per eliminare le dipendenze incrociate.


7
Penso che uno dei motivi per cui hanno creato questa funzionalità linguistica sia@IBOutlet
Jiaaro,

11
+1 per l'avvertenza "TUTTAVIA". Potrebbe non essere vero tutto il tempo, ma è certamente qualcosa da cercare.
JMD,

4
Hai ancora un forte ciclo di riferimento tra A e B. Gli opzionali implicitamente non scartati NON creano un riferimento debole. Devi ancora dichiarare myByddyA o myBuddyB come deboli (probabilmente myBuddyA)
drewag

6
Per essere ancora più chiari sul perché questa risposta sia errata e pericolosamente fuorviante: gli Optionals implicitamente non confezionati non hanno assolutamente nulla a che fare con la gestione della memoria e non impediscono i cicli di mantenimento. Tuttavia, gli Optional implicitamente non confezionati sono ancora utili nelle circostanze descritte per l'impostazione del riferimento bidirezionale. Quindi semplicemente aggiungendo la weakdichiarazione e rimuovendo "senza creare un forte ciclo di conservazione"
Drewag,

1
@drewag: hai ragione - ho modificato la risposta per rimuovere il ciclo di mantenimento. Intendevo indebolire il backreference ma immagino che mi sia sfuggito di mente.
n8gray,

37

Gli optionals implicitamente non confezionati sono un compromesso pragmatico per rendere più piacevole il lavoro in ambiente ibrido che deve interagire con i framework Cocoa esistenti e le loro convenzioni, consentendo anche una migrazione graduale verso un paradigma di programmazione più sicuro - senza puntatori null - applicato dal compilatore Swift.

Il libro Swift, nel capitolo The Basics , nella sezione Optionals implicitamente non confezionati dice:

Gli optionals implicitamente non scartati sono utili quando si conferma che il valore di un optional esiste immediatamente dopo che è stato definito per la prima volta e si può presumere che esista in ogni punto successivo. L'uso primario di opzioni non implicitamente implicite in Swift è durante l'inizializzazione della classe, come descritto in Riferimenti noti e Proprietà facoltative non implicite .

Puoi pensare a un optional implicitamente da scartare come dare l'autorizzazione per il facoltativo di essere scartato automaticamente ogni volta che viene usato. Invece di posizionare un punto esclamativo dopo il nome dell'opzione ogni volta che lo si utilizza, si inserisce un punto esclamativo dopo il tipo di opzione quando lo si dichiara.

Questo si riduce ai casi d'uso in cui la non nil- proprietà delle proprietà viene stabilita tramite la convenzione di utilizzo e non può essere applicata dal compilatore durante l'inizializzazione della classe. Ad esempio, le UIViewControllerproprietà inizializzate da NIB o Storyboard, in cui l'inizializzazione viene suddivisa in fasi separate, ma dopo viewDidLoad()si può presumere che le proprietà generalmente esistano. Altrimenti, per soddisfare il compilatore, è stato necessario utilizzare il wrapping forzato , l'associazione facoltativa o il concatenamento opzionale solo per oscurare lo scopo principale del codice.

Sopra la parte del libro Swift fa riferimento anche al capitolo Conteggio automatico dei riferimenti :

Tuttavia, esiste un terzo scenario, in cui entrambe le proprietà devono sempre avere un valore e nessuna proprietà dovrebbe mai essere niluna volta completata l'inizializzazione. In questo scenario, è utile combinare una proprietà sconosciuta in una classe con una proprietà facoltativa non implicitamente scartata nell'altra classe.

Ciò consente di accedere direttamente a entrambe le proprietà (senza lo scartamento opzionale) una volta completata l'inizializzazione, evitando comunque un ciclo di riferimento.

Questo si riduce alla stranezza di non essere un linguaggio spazzatura, quindi l'interruzione dei cicli di mantenimento dipende da te come programmatore e gli optional implicitamente non confezionati sono uno strumento per nascondere questa stranezza.

Che copre il "Quando utilizzare gli opzionali implicitamente non scartati nel tuo codice?" domanda. Come sviluppatore di applicazioni, li incontrerai principalmente nelle firme dei metodi delle librerie scritte in Objective-C, che non ha la capacità di esprimere tipi opzionali.

Da Uso di Swift con Cocoa e Objective-C, sezione Operazioni con zero :

Poiché Objective-C non garantisce che un oggetto sia diverso da zero, Swift rende tutte le classi nei tipi di argomento e i tipi restituiti opzionali nelle API Objective-C importate. Prima di utilizzare un oggetto Objective-C, è necessario verificare che non sia mancante.

In alcuni casi, potresti essere assolutamente certo che un metodo o una proprietà Objective-C non restituisce mai un nilriferimento a un oggetto. Per rendere gli oggetti in questo scenario speciale più comodi con cui lavorare, Swift importa i tipi di oggetti come opzioni implicitamente da scartare . I tipi opzionali implicitamente non confezionati includono tutte le caratteristiche di sicurezza dei tipi opzionali. Inoltre, è possibile accedere direttamente al valore senza verificarenilo scartalo tu stesso. Quando si accede al valore in questo tipo di tipo opzionale senza prima scartarlo in modo sicuro, l'opzione facoltativamente non scartata verifica se il valore è mancante. Se il valore non è presente, si verifica un errore di runtime. Di conseguenza, dovresti sempre controllare e scartare tu stesso un'opzione implicitamente non scartata, a meno che tu non sia sicuro che il valore non possa mancare.

... e oltre qui giaceva draghi


Grazie per questa risposta approfondita. Riesci a pensare a una rapida lista di controllo su quando usare un Optional implicitamente non confezionato e quando sarebbe sufficiente una variabile standard?
Hairgami_Master

@Hairgami_Master Ho aggiunto la mia risposta con un elenco e esempi concreti
drewag

18

Esempi semplici di una riga (o di più righe) non trattano molto bene il comportamento degli optionals - sì, se dichiari una variabile e fornisci subito un valore, non c'è motivo in un opzionale.

Il caso migliore che ho visto finora è l'installazione che si verifica dopo l'inizializzazione dell'oggetto, seguita dall'uso "garantito" per seguire quella configurazione, ad esempio in un controller di visualizzazione:

class MyViewController: UIViewController {

    var screenSize: CGSize?

    override func viewDidLoad {
        super.viewDidLoad()
        screenSize = view.frame.size
    }

    @IBAction printSize(sender: UIButton) {
        println("Screen size: \(screenSize!)")
    }
}

Sappiamo che printSizeverrà chiamato dopo il caricamento della vista: è un metodo di azione collegato a un controllo all'interno di quella vista e ci siamo assicurati di non chiamarlo diversamente. Quindi possiamo salvarci un po 'di controllo / associazione opzionale con! . Swift non è in grado di riconoscere tale garanzia (almeno fino a quando Apple non risolverà il problema di arresto), quindi dici al compilatore che esiste.

Ciò rompe in qualche modo la sicurezza dei tipi. Ovunque tu abbia un'opzione implicitamente da scartare è un posto dove la tua app può andare in crash se la tua "garanzia" non è sempre valida, quindi è una funzione da usare con parsimonia. Inoltre, usare !tutto il tempo fa sembrare che tu stia urlando, e a nessuno piace.


Perché non solo inizializzare screenSize su CGSize (altezza: 0, larghezza: 0) e salvare il problema di dover urlare la variabile ogni volta che si accede ad essa?
Martin Gordon,

Una dimensione potrebbe non essere stata il miglior esempio, dal momento che CGSizeZeropuò essere un buon valore sentinella nell'uso del mondo reale. Ma cosa succede se si ha una dimensione caricata dal pennino che potrebbe effettivamente essere zero? Quindi l'utilizzo CGSizeZerocome sentinella non consente di distinguere tra un valore non impostato e impostato su zero. Inoltre, questo si applica ugualmente bene ad altri tipi caricati dal pennino (o in qualsiasi altro posto successivo init): stringhe, riferimenti a sottopagine, ecc.
rickster,

2
Parte del punto di Opzionale nei linguaggi funzionali è di non avere valori sentinella. O hai un valore o no. Non dovresti avere un caso in cui hai un valore che indica un valore mancante.
Wayne Tanner,

4
Penso che tu abbia frainteso la domanda del PO. Il PO non sta chiedendo il caso generale per gli opzionali, piuttosto, in particolare la necessità / l'uso di opzionali implicitamente non scartati (cioè no let foo? = 42, piuttosto let foo! = 42). Questo non risolve questo problema. (Questa potrebbe essere una risposta pertinente sugli opzionali, intendiamoci, ma non sugli opzionali implicitamente da scartare che sono un animale diverso / correlato.)
JMD

15

Apple fornisce un ottimo esempio in Il linguaggio di programmazione Swift -> Conteggio riferimenti automatico -> Risoluzione di cicli di riferimento forti tra istanze di classe -> Riferimenti noti e Proprietà facoltative non implicite

class Country {
    let name: String
    var capitalCity: City! // Apple finally correct this line until 2.0 Prerelease (let -> var)
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
}

class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

L'inizializzatore per Cityviene chiamato dall'inizializzatore per Country. Tuttavia, l'inizializzatore per Countrynon può passare selfalla Cityinizializzatore fino ad una nuova Countryistanza viene inizializzato completamente, come descritto in due fasi di inizializzazione .

Per far fronte a questo requisito, devi dichiarare la capitalCityproprietà Countrycome proprietà facoltativa non implicita.


Il tutorial menzionato in questa risposta è qui .
Franklin Yu,

3

La logica degli optionals impliciti è più facile da spiegare guardando prima la logica per lo scartamento forzato.

Disimballaggio forzato di un facoltativo (implicito o no), usando il! operatore, significa che sei certo che il tuo codice non ha bug e che l'opzione ha già un valore in cui viene scartata. Senza il ! operatore, potresti semplicemente affermare con un'associazione opzionale:

 if let value = optionalWhichTotallyHasAValue {
     println("\(value)")
 } else {
     assert(false)
 }

che non è bello come

println("\(value!)")

Ora, gli optionals impliciti ti consentono di esprimere un optional che ti aspetti di avere sempre un valore quando non viene scartato, in tutti i flussi possibili. Quindi è solo un passo avanti nell'aiutarti - rilassando il requisito di scrivere il! per scartare ogni volta e assicurando che il runtime continui ad errori nel caso in cui i presupposti sul flusso siano errati.


1
@newacct: il non-zero in tutti i flussi possibili attraverso il codice non è uguale al non-zero dall'inizializzazione del genitore (classe / struttura) attraverso la deallocazione. Interface Builder è il classico esempio (ma ci sono molti altri schemi di inizializzazione ritardata): se una classe viene sempre usata da un pennino, allora le variabili di outlet non verranno impostate init(che potresti non implementare), ma ' è garantito per essere impostato dopo awakeFromNib/ viewDidLoad.
rickster,

3

Se si sa per certo, un valore di ritorno da un optional, invece di nil, implicitamente non imbustati Optionals usare per catturare direttamente quei valori da optional e non optionals non possono.

//Optional string with a value
let optionalString: String? = "This is an optional String"

//Declaration of an Implicitly Unwrapped Optional String
let implicitlyUnwrappedOptionalString: String!

//Declaration of a non Optional String
let nonOptionalString: String

//Here you can catch the value of an optional
implicitlyUnwrappedOptionalString = optionalString

//Here you can't catch the value of an optional and this will cause an error
nonOptionalString = optionalString

Quindi questa è la differenza tra l'uso di

let someString : String! e let someString : String


1
Questo non risponde alla domanda del PO. OP sa cos'è un Opzionale implicitamente non confezionato.
Franklin Yu,

0

Penso che Optionalsia un brutto nome per questo costrutto che confonde molti principianti.

Altre lingue (ad esempio Kotlin e C #) usano il termine Nullablee lo rende molto più facile da capire.

Nullablesignifica che puoi assegnare un valore nullo a una variabile di questo tipo. Quindi, se lo è Nullable<SomeClassType>, puoi assegnargli dei null, se è giusto SomeClassType, non puoi. Ecco come funziona Swift.

Perché usarli? Bene, a volte devi avere dei null, ecco perché. Ad esempio, quando sai che vuoi avere un campo in una classe, ma non puoi assegnarlo a nulla quando crei un'istanza di quella classe, ma lo farai in seguito. Non darò esempi, perché le persone li hanno già forniti qui. Sto solo scrivendo questo per dare i miei 2 centesimi.

A proposito, ti suggerisco di vedere come funziona in altre lingue, come Kotlin e C #.

Ecco un link che spiega questa funzione in Kotlin: https://kotlinlang.org/docs/reference/null-safety.html

Altre lingue, come Java e Scala, hanno Optionals, ma funzionano in modo diverso da Optionals in Swift, perché i tipi Java e Scala sono tutti nulli per impostazione predefinita.

Tutto sommato, penso che questa funzione avrebbe dovuto essere nominata Nullablein Swift, e non Optional...


0

Implicitly Unwrapped Optionalè uno zucchero sintattico per Optionalnon forzare un programmatore a scartare una variabile. Può essere usato per una variabile che non può essere inizializzata durante two-phase initialization processe implica un valore nullo. Questa variabile si comporta come nulla, ma in realtà è una variabile opzionale . Un buon esempio è: i punti vendita di Interface Builder

Optional di solito sono preferibili

var nonNil: String = ""
var optional: String?
var implicitlyUnwrappedOptional: String!

func foo() {
    //get a value
    nonNil.count
    optional?.count

    //Danderour - makes a force unwrapping which can throw a runtime error
    implicitlyUnwrappedOptional.count

    //assign to nil
//        nonNil = nil //Compile error - 'nil' cannot be assigned to type 'String'
    optional = nil
    implicitlyUnwrappedOptional = nil
}
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.