wake-up-neo.net

Wann sollte ich einen optionalen Wert mit Null vergleichen?

Oft müssen Sie folgenden Code schreiben:

if someOptional != nil {
    // do something with the unwrapped someOptional e.g.       
    someFunction(someOptional!)
}

Dies scheint etwas wortreich zu sein, und ich höre auch, dass die Verwendung des Unlock-Operators ! unsicher und am besten vermieden werden kann. Gibt es einen besseren Weg, um damit umzugehen?

40

Es ist fast immer unnötig zu prüfen, ob ein optionales nicht nil ist. Das ist so ziemlich das einzige Mal, dass Sie dies tun müssen, wenn die nil -Ness die einzige Sache ist, über die Sie wissen möchten - Sie Es ist mir egal, was im Wert ist, nur dass es nicht nil ist.

Unter den meisten anderen Umständen gibt es eine Abkürzung für Swift, die die Aufgabe in if sicherer und präziser für Sie erledigen kann.

Verwenden des Werts, wenn er nicht nil ist

Anstatt von:

let s = "1"
let i = Int(s)

if i != nil {
    print(i! + 1)
}

sie können if let verwenden:

if let i = Int(s) {
    print(i + 1)
}

Sie können auch var verwenden:

if var i = Int(s) {
    print(++i)  // prints 2
}

beachten Sie jedoch, dass i eine lokale Kopie ist. Änderungen an i wirken sich nicht auf den Wert im Original aus Optional.

Sie können mehrere Optionen in einem einzigen if let Auspacken, und spätere können von früheren abhängen:

if let url = NSURL(string: urlString),
       data = NSData(contentsOfURL: url),
       image = UIImage(data: data)
{
    let view = UIImageView(image: image)
    // etc.
}

Sie können den nicht umbrochenen Werten auch where -Klauseln hinzufügen:

if let url = NSURL(string: urlString) where url.pathExtension == "png",
   let data = NSData(contentsOfURL: url), image = UIImage(data: data)
{ etc. }

Ersetzen von nil durch einen Standardwert

Anstatt von:

let j: Int
if i != nil {
    j = i
}
else {
    j = 0
}

oder:

let j = i != nil ? i! : 0

sie können den Null-Koaleszenz-Operator ?? verwenden:

// j will be the unwrapped value of i,
// or 0 if i is nil
let j = i ?? 0

Gleichsetzen eines optionalen mit einem nicht optionalen

Anstatt von:

if i != nil && i! == 2 {
    print("i is two and not nil")
}

sie können überprüfen, ob die optionalen Werte nicht-optionalen Werten entsprechen:

if i == 2 {
    print("i is two and not nil")
}

Dies funktioniert auch mit Vergleichen:

if i < 5 { }

nil ist immer gleich nils und ist kleiner als jeder andere Wert als nil.

Achtung! Hier kann es Fallstricke geben:

let a: Any = "hello"
let b: Any = "goodbye"
if (a as? Double) == (b as? Double) {
    print("these will be equal because both nil...")
}

Aufrufen einer Methode (oder Lesen einer Eigenschaft) für ein optionales

Anstatt von:

let j: Int
if i != nil {
    j = i.successor()
}
else {
   // no reasonable action to take at this point
   fatalError("no idea what to do now...")
}

sie können die optionale Verkettung ?. verwenden:

let j = i?.successor()

Beachten Sie, dass j jetzt auch optional ist, um das Szenario fatalError zu berücksichtigen. Später können Sie eine der anderen Techniken in dieser Antwort verwenden, um die Optionalität von j zu behandeln. Oft können Sie jedoch das eigentliche Auspacken Ihrer Optionen bis viel später oder manchmal gar nicht verschieben.

Wie der Name schon sagt, können Sie sie verketten, so dass Sie schreiben können:

let j = s.toInt()?.successor()?.successor()

Optionale Verkettung funktioniert auch mit Indizes:

let dictOfArrays: ["nine": [0,1,2,3,4,5,6,7]]
let sevenOfNine = dictOfArrays["nine"]?[7]  // returns {Some 7}

und Funktionen:

let dictOfFuncs: [String:(Int,Int)->Int] = [
      "add":(+),
      "subtract":(-)
]

dictOfFuncs["add"]?(1,1)  // returns {Some 2}

Einer Eigenschaft auf einem optionalen zuweisen

Anstatt von:

if splitViewController != nil {
    splitViewController!.delegate = self 
}

sie können durch eine optionale Kette zuweisen:

splitViewController?.delegate = self

Nur wenn splitViewController nicht -nil ist, erfolgt die Zuweisung.

Verwenden des Werts, wenn es nicht nil oder Bailing ist (neu in Swift 2.0)

Manchmal möchten Sie in einer Funktion einen kurzen Code schreiben, um ein optionales Element zu aktivieren. Wenn dies nil ist, beenden Sie die Funktion frühzeitig, andernfalls fahren Sie fort.

Sie könnten dies so schreiben:

func f(s: String) {
    let i = Int(s)
    if i == nil { fatalError("Input must be a number") }
    print(i! + 1)
}

oder um das gewaltsame Auspacken zu vermeiden, wie folgt:

func f(s: String) {
    if let i = Int(s) {
        print(i! + 1)
    }
    else { 
        fatalErrr("Input must be a number")
    }
}

es ist jedoch viel angenehmer, den Fehlerbehandlungscode bei der Überprüfung ganz oben zu belassen. Dies kann auch zu einem unangenehmen Nisten führen (die "Pyramide des Schicksals").

Stattdessen können Sie guard verwenden, was einem if not let Ähnelt:

func f(s: String) {
    guard let i = Int(s)
        else { fatalError("Input must be a number") }

    // i will be an non-optional Int
    print(i+1)
}

Der Teil else muss den Bereich des geschützten Werts verlassen, z. a return oder fatalError, um sicherzustellen, dass der geschützte Wert für den Rest des Bereichs gültig ist.

guard ist nicht auf den Funktionsumfang beschränkt. Zum Beispiel das Folgende:

var a = ["0","1","foo","2"]
while !a.isEmpty  {
    guard let i = Int(a.removeLast())
        else { continue }

    print(i+1, appendNewline: false)
}

druckt 321.

Nicht-Null-Elemente in einer Sequenz durchlaufen (neu in Swift 2.0)

Wenn Sie eine Folge von Optionen haben, können Sie mit for case let _? Alle nicht optionalen Elemente durchlaufen:

let a = ["0","1","foo","2"]
for case let i? in a.map({ Int($0)}) {
    print(i+1, appendNewline: false)
}

druckt 321. Hierbei wird die Mustervergleichssyntax für ein optionales Element verwendet. Dies ist ein Variablenname, gefolgt von ?.

Sie können diesen Mustervergleich auch in switch -Anweisungen verwenden:

func add(i: Int?, _ j: Int?) -> Int? {
    switch (i,j) {
    case (nil,nil), (_?,nil), (nil,_?):
        return nil
    case let (x?,y?):
        return x + y
    }
}

add(1,2)    // 3
add(nil, 1) // nil

Schleifen, bis eine Funktion nil zurückgibt

Ähnlich wie bei if let Können Sie auch while let Schreiben und eine Schleife ausführen, bis nil:

while let line = readLine() {
    print(line)
}

Sie können auch while var Schreiben (ähnliche Vorsichtsmaßnahmen wie bei if var).

where -Klauseln funktionieren auch hier (und beenden die Schleife, anstatt zu überspringen):

while let line = readLine() 
where !line.isEmpty {
    print(line)
}

Übergeben eines optionalen Werts an eine Funktion, die einen nicht optionalen Wert annimmt und ein Ergebnis zurückgibt

Anstatt von:

let j: Int
if i != nil {
    j = abs(i!)
}
else {
   // no reasonable action to take at this point
   fatalError("no idea what to do now...")
}

sie können optional den map -Operator verwenden:

let j = i.map { abs($0) }

Dies ist der optionalen Verkettung sehr ähnlich, aber wenn Sie den nicht optionalen Wert als Argument an die Funktion übergeben müssen . Wie bei der optionalen Verkettung ist das Ergebnis optional.

Das ist schön, wenn man sowieso ein optionales möchte. Beispielsweise ist reduce1 Wie reduce, verwendet jedoch den ersten Wert als Startwert und gibt ein optionales zurück, falls das Array leer ist. Du könntest es so schreiben (mit dem guard Schlüsselwort von früher):

extension Array {
    func reduce1(combine: (T,T)->T)->T? {

        guard let head = self.first
            else { return nil }

        return dropFirst(self).reduce(head, combine: combine)
    }
}

[1,2,3].reduce1(+) // returns 6

Stattdessen können Sie die Eigenschaft .firstmap und Folgendes zurückgeben:

extension Array {
    func reduce1(combine: (T,T)->T)->T? {
        return self.first.map {
            dropFirst(self).reduce($0, combine: combine)
        }
    }
}

Übergeben eines optionalen Werts an eine Funktion, die einen optionalen Wert annimmt und ein Ergebnis zurückgibt, um lästige doppelte Optionen zu vermeiden

Manchmal möchten Sie etwas Ähnliches wie map, aber die Funktion, die Sie selbst aufrufen möchten , gibt ein optionales zurück. Zum Beispiel:

// an array of arrays
let arr = [[1,2,3],[4,5,6]]
// .first returns an optional of the first element of the array
// (optional because the array could be empty, in which case it's nil)
let fst = arr.first  // fst is now [Int]?, an optional array of ints
// now, if we want to find the index of the value 2, we could use map and find
let idx = fst.map { find($0, 2) }

Aber jetzt ist idx vom Typ Int??, Eine doppelte Option. Stattdessen können Sie flatMap verwenden, wodurch das Ergebnis zu einem einzigen optionalen Ergebnis „geglättet“ wird:

let idx = fst.flatMap { find($0, 2) }
// idx will be of type Int? 
// and not Int?? unlike if `map` was used
109

Ich denke, du solltest zum Swift-Programmierbuch zurückkehren und lernen, wozu diese Dinge gehören. ! wird verwendet, wenn Sie absolut sicher sind, dass das optionale Element nicht null ist. Da Sie erklärt haben, dass Sie absolut sicher sind, stürzt es ab, wenn Sie sich irren. Welches ist völlig beabsichtigt. Es ist "unsicher und am besten vermieden" in dem Sinne, dass Behauptungen in Ihrem Code "unsicher und am besten vermieden" sind. Zum Beispiel: 

if someOptional != nil {
    someFunction(someOptional!)
}

Das ! ist absolut sicher. Es sei denn, es liegt ein großer Fehler in Ihrem Code vor, z. B. wenn Sie versehentlich schreiben (ich hoffe, Sie erkennen den Fehler).

if someOptional != nil {
    someFunction(SomeOptional!)
}

in diesem Fall kann Ihre App abstürzen, Sie untersuchen, warum sie abstürzt, und Sie beheben den Fehler. Genau dafür gibt es den Absturz. Ein Ziel von Swift ist es, dass Ihre App offensichtlich ordnungsgemäß funktionieren sollte. Da Swift dies jedoch nicht erzwingen kann, erzwingt es, dass Ihre App entweder ordnungsgemäß funktioniert oder abstürzt, wenn dies möglich ist. Bugs werden daher vor der Auslieferung der App entfernt. 

2
gnasher729

Sie gibt es einen Weg. Es heißt Optionale Verkettung . Aus der Dokumentation:

Optionale Verkettung ist ein Prozess zum Abfragen und Aufrufen von Eigenschaften Methoden und Subskripte auf einem optionalen, der derzeit null sein kann. Ob Das optionale Element enthält einen Wert, die Eigenschaft, die Methode oder den Indexaufruf gelingt es; Wenn die Option null ist, gilt die Eigenschaft, Methode oder das Index call gibt null zurück. Mehrere Abfragen können miteinander verkettet werden, und die Die gesamte Kette fällt ordnungsgemäß aus, wenn ein Glied in der Kette gleich Null ist.

Hier ist ein Beispiel

class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
}

let john = Person()

if let roomCount = john.residence?.numberOfRooms {
    println("John's residence has \(roomCount) room(s).")
} else {
    println("Unable to retrieve the number of rooms.")
}
// prints "Unable to retrieve the number of rooms."

Sie können den vollständigen Artikel hier überprüfen.

0
Sergey Pekar

Nach langem Nachdenken und Nachforschen habe ich den einfachsten Weg gefunden, ein optionales Zubehör auszupacken: 

  • Erstellen Sie eine neue Swift-Datei und nennen Sie sie UnwrapOperator.Swift

  • Fügen Sie den folgenden Code in die Datei ein:

    import Foundation
    import UIKit
    
    protocol OptionalType { init() }
    
    extension String: OptionalType {}
    extension Int: OptionalType {}
    extension Int64: OptionalType {}
    extension Float: OptionalType {}
    extension Double: OptionalType {}
    extension CGFloat: OptionalType {}
    extension Bool: OptionalType {}
    extension UIImage : OptionalType {}
    extension IndexPath : OptionalType {}
    extension NSNumber : OptionalType {}
    extension Date : OptionalType {}
    extension UIViewController : OptionalType {}
    
    postfix operator *?
    postfix func *?<T: OptionalType>( lhs: T?) -> T {
    
        guard let validLhs = lhs else { return T() }
        return validLhs
    }
    
    prefix operator /
    prefix func /<T: OptionalType>( rhs: T?) -> T {
    
        guard let validRhs = rhs else { return T() }
        return validRhs
    }
    
  • Der obige Code hat nun 2 Operator [Ein Präfix und ein Postfix] erstellt. 

  • Zum Zeitpunkt des Auspackens können Sie einen dieser Operatoren vor oder nach den Optionals verwenden 
  • Die Erklärung ist einfach: Die Operatoren geben den constructor-Wert zurück, wenn sie in der Variablen nil erhalten, ansonsten der enthaltene Wert in der Variablen.

  • Unten ist das Anwendungsbeispiel: 

    var a_optional : String? = "abc"
    var b_optional : Int? = 123
    
    // before the usage of Operators
    
    print(a_optional) --> Optional("abc")
    print(b_optional) --> Optional(123)
    
    // Prefix Operator Usage
    
    print(/a_optional) --> "abc"
    print(/b_optional) --> 123
    
    // Postfix Operator Usage
    
    print(a_optional*?) --> "abc"
    print(b_optional*?) --> 123
    
  • Unten ist das Beispiel, wenn variable nil enthält: 

    var a_optional : String? = nil
    var b_optional : Int? = nil
    
    // before the usage of Operators
    
    print(a_optional) --> nil
    print(b_optional) --> nil
    
    // Prefix Operator Usage
    
    print(/a_optional) --> ""
    print(/b_optional) --> 0
    
    // Postfix Operator Usage
    
    print(a_optional*?) --> ""
    print(b_optional*?) --> 0
    
  • Sie haben nun die Wahl, welchen Operator Sie verwenden, und beide dienen demselben Zweck.

0
Mr. Bean

Wir können eine optionale Bindung verwenden.

var x:Int?

if let y = x {
  // x was not nil, and its value is now stored in y
}
else {
  // x was nil
}
0
13th Ghost