Swift: testowanie opcji pod kątem zera


137

Używam Xcode 6 Beta 4. Mam dziwną sytuację, w której nie mogę dowiedzieć się, jak odpowiednio przetestować opcje.

Jeśli mam opcjonalny xyz, to właściwy sposób na przetestowanie:

if (xyz) // Do something

lub

if (xyz != nil) // Do something

Dokumenty mówią, że należy to zrobić w pierwszy sposób, ale odkryłem, że czasami drugi sposób jest wymagany i nie generuje błędu kompilatora, ale czasami drugi sposób generuje błąd kompilatora.

Mój konkretny przykład to użycie parsera GData XML zmostkowanego do Swift:

let xml = GDataXMLDocument(
    XMLString: responseBody,
    options: 0,
    error: &xmlError);

if (xmlError != nil)

Tutaj, gdybym właśnie to zrobił:

if xmlError

zawsze zwracała prawdę. Jeśli jednak:

if (xmlError != nil)

wtedy to działa (tak jak to działa w Objective-C).

Czy jest coś w GData XML i sposobie, w jaki traktuje opcje, których mi brakuje?


4
Czy możemy zobaczyć pełny przykład nieoczekiwanego przypadku i przypadków błędów? (I wiem, że to trudne, ale spróbuj zacząć tracić nawiasy wokół warunków warunkowych!)
Matt Gibson

1
zostało to zmienione w Xcode 6 beta 5
newacct


Właśnie zaktualizowałem pytanie o nieoczekiwany przypadek.
tng

Nie zaktualizowałem jeszcze do wersji beta 5. Zrobię to wkrótce; miło widzieć ?? w Swift i bardziej spójne opcjonalne zachowanie.
tng

Odpowiedzi:


172

W Xcode Beta 5 nie pozwalają już na:

var xyz : NSString?

if xyz {
  // Do something using `xyz`.
}

Powoduje to błąd:

nie jest zgodny z protokołem „BooleanType.Protocol”

Musisz użyć jednego z tych formularzy:

if xyz != nil {
   // Do something using `xyz`.
}

if let xy = xyz {
   // Do something using `xy`.
}

5
Czy ktoś może wyjaśnić, dlaczego xyz == nil nie działa?
Christoph

Drugi przykład powinien brzmieć: // Zrób coś używając xy (co jest główną różnicą w przypadkach użycia między tymi dwoma wariantami)
Alper

@Christoph: Komunikat o błędzie jest taki, że stała „xyz” została użyta przed zainicjalizowaniem. Myślę, że musisz dodać '= nil' na końcu wiersza deklaracji xyz.
user3207158

Dla tych, którzy są nowicjuszami w szybkości jak ja i zastanawiają się: nadal musisz rozpakować xyz wewnątrz bloku if. Jest to jednak bezpieczne, nawet jeśli wymusisz rozpakowanie
Omar

@Omar Oto piękno drugiej formy, używasz xy wewnątrz bloku bez konieczności jego rozpakowywania.
Erik Doernenburg

24

Aby dodać do innych odpowiedzi, zamiast przypisywać do zmiennej o innej nazwie wewnątrz ifwarunku:

var a: Int? = 5

if let b = a {
   // do something
}

możesz ponownie użyć tej samej nazwy zmiennej, na przykład:

var a: Int? = 5

if let a = a {
    // do something
}

Pomoże Ci to uniknąć wyczerpania nazw zmiennych kreacji ...

Wykorzystuje to zmienne cieniowanie, które jest obsługiwane w języku Swift.


18

Jednym z najbardziej bezpośrednich sposobów korzystania z opcji jest:

Założenie xyzjest opcjonalne, jak Int?na przykład.

if let possXYZ = xyz {
    // do something with possXYZ (the unwrapped value of xyz)
} else {
    // do something now that we know xyz is .None
}

W ten sposób możesz zarówno sprawdzić, czy xyzzawiera wartość, a jeśli tak, natychmiast pracować z tą wartością.

Jeśli chodzi o błąd kompilatora, typ UInt8nie jest opcjonalny (uwaga nr „?”) I dlatego nie można go przekonwertować na nil. Upewnij się, że zmienna, z którą pracujesz, jest opcjonalna, zanim potraktujesz ją jako jedną.


1
Uważam, że ta metoda jest zła, ponieważ niepotrzebnie tworzę nową zmienną (stałą) i muszę tworzyć nową nazwę, która przez większość czasu będzie gorsza niż poprzednia. Pisanie zajmuje mi więcej czasu i dla czytelnika.
Lombas

3
Jest to standardowa i zalecana metoda rozpakowywania opcjonalnej zmiennej. „Jeśli niech” jest naprawdę potężne.
gnasher729

@ gnasher729, ale co, jeśli chcesz użyć tylko drugiej części
Brad Thomas,

15

Swift 3.0, 4.0

Istnieją głównie dwa sposoby sprawdzenia, czy nil jest opcjonalny. Oto przykłady z porównaniem między nimi

1. jeśli niech

if letto najbardziej podstawowy sposób sprawdzania, czy nil jest opcjonalny. Do tego czeku zerowego można dołączyć inne warunki, oddzielone przecinkami. Zmienna nie może być zerowa, aby przejść do następnego warunku. Jeśli wymagane jest tylko sprawdzenie zerowe, usuń dodatkowe warunki w poniższym kodzie.

Poza tym, jeśli xnie jest zerowe, zamknięcie if zostanie wykonane i x_valbędzie dostępne w środku. W przeciwnym razie następuje zamknięcie else.

if let x_val = x, x_val > 5 {
    //x_val available on this scope
} else {

}

2. niech strażnik

guard letmogą robić podobne rzeczy. Jego głównym celem jest uczynienie go bardziej logicznym. To tak, jakby powiedzieć , że zmienna nie jest zerowa, w przeciwnym razie zatrzymaj funkcję . guard letmoże również sprawdzić dodatkowe warunki jako if let.

Różnice polegają na tym, że rozpakowana wartość będzie dostępna w tym samym zakresie, co guard let, jak pokazano w komentarzu poniżej. Prowadzi to również do tego stopnia, że w innym zamknięciem, program musi wyjść z obecnego zakresu, przez return, breakitd

guard let x_val = x, x_val > 5 else {
    return
}
//x_val available on this scope

11

Z szybkiego przewodnika po programowaniu

Instrukcje if i wymuszone rozpakowywanie

Możesz użyć instrukcji if, aby dowiedzieć się, czy opcja opcjonalna zawiera wartość. Jeśli opcjonalny ma wartość, przyjmuje wartość true; jeśli nie ma żadnej wartości, przyjmuje wartość fałsz.

Więc najlepszym sposobem na to jest

// swift > 3
if xyz != nil {}

a jeśli używasz instrukcji xyzin if, możesz wtedy rozpakować xyzinstrukcję if w zmiennej stałej. Więc nie musisz rozpakowywać każdego miejsca w instrukcji if, gdzie xyzjest używana.

if let yourConstant = xyz{
      //use youtConstant you do not need to unwrap `xyz`
}

Ta konwencja jest sugerowana appleprzez deweloperów i będą ją stosować.


2
W Swift 3 musisz to zrobić if xyz != nil {...} .
psmythirl

W Swift 3 opcje nie mogą być używane jako wartości logiczne. Po pierwsze, ponieważ jest to C-izm, który nigdy nie powinien być w języku, po pierwsze, a po drugie, ponieważ powoduje całkowite zamieszanie z opcjonalnym Boolem.
gnasher729

9

Chociaż nadal musisz jawnie porównać opcjonalne z nillub użyć opcjonalnego wiązania, aby dodatkowo wyodrębnić jego wartość (tj. Opcje opcjonalne nie są niejawnie konwertowane na wartości logiczne), warto zauważyć, że Swift 2 dodał guardinstrukcję, aby pomóc uniknąć piramidy zagłady podczas pracy z wieloma opcjonalnymi wartościami.

Innymi słowy, dostępne opcje obejmują teraz jawne sprawdzenie nil:

if xyz != nil {
    // Do something with xyz
}

Opcjonalne wiązanie:

if let xyz = xyz {
    // Do something with xyz
    // (Note that we can reuse the same variable name)
}

I guardoświadczenia:

guard let xyz = xyz else {
    // Handle failure and then exit this code block
    // e.g. by calling return, break, continue, or throw
    return
}

// Do something with xyz, which is now guaranteed to be non-nil

Zwróć uwagę, jak zwykłe opcjonalne powiązanie może prowadzić do większego wcięcia, gdy istnieje więcej niż jedna opcjonalna wartość:

if let abc = abc {
    if let xyz = xyz {
        // Do something with abc and xyz
    }        
}

Możesz uniknąć tego zagnieżdżania za pomocą guardinstrukcji:

guard let abc = abc else {
    // Handle failure and then exit this code block
    return
}

guard let xyz = xyz else {
    // Handle failure and then exit this code block
    return
}

// Do something with abc and xyz

1
jak wspomniano powyżej, możesz to również zrobić, aby pozbyć się zagnieżdżonych opcjonalnych statystyk. if let abc = abc? .xyz? .something {}
Suhaib

1
@Suhaib Ach, prawda: możesz również użyć opcjonalnego łączenia łańcuchowego ( developer.apple.com/library/prerelease/content/documentation/ ... ), aby szybko uzyskać dostęp do zagnieżdżonych właściwości. Nie wspomniałem o tym tutaj, ponieważ myślałem, że może to być zbyt styczne z pierwotnego pytania.
Chris Frederick,

Świetna odpowiedź! Ale Swift, OMG ... nadal daleko do bycia przyjaznym dla programistów!
test turinga

@turingtested Thanks! Właściwie myślę, że jest to przyjazne programistom w tym sensie, że gwarantuje, że nie spróbujesz przypadkowo użyć nilwartości, ale zgadzam się, że krzywa uczenia się jest nieco stroma. :)
Chris Frederick

4

Rozszerzenie protokołu Swift 5

Oto podejście wykorzystujące rozszerzenie protokołu, dzięki czemu można łatwo wstawić opcjonalną kontrolę zerową:

import Foundation

public extension Optional {

    var isNil: Bool {

        guard case Optional.none = self else {
            return false
        }

        return true

    }

    var isSome: Bool {

        return !self.isNil

    }

}

Stosowanie

var myValue: String?

if myValue.isNil {
    // do something
}

if myValue.isSome {
    // do something
}

Otrzymałem kilka głosów przeciw tej odpowiedzi i chciałbym wiedzieć, dlaczego. Przynajmniej skomentuj, jeśli zamierzasz głosować przeciw.
Brody Robertson

1
Jest to prawdopodobnie odpowiednik swifttego, pythonistasktóry próbuje zachować język w jakiejś formie pytonicznej czystości. Moje podanie? Właśnie umieściłem twój kod w moim miksie Utils.
javadba

2

Teraz możesz szybko wykonać następującą rzecz, która pozwoli ci odzyskać odrobinę celu-c if nil else

if textfieldDate.text?.isEmpty ?? true {

}

2

Zamiast tego ifoperator potrójny może się przydać, gdy chcesz uzyskać wartość na podstawie tego, czy coś jest zerowe:

func f(x: String?) -> String {
    return x == nil ? "empty" : "non-empty"
}

xyz == nilwyrażenie nie działa, ale x != nildziała. Nie wiem dlaczego.
Andrew,

2

Innym podejściem, oprócz używania instrukcji iflub guarddo wykonania opcjonalnego wiązania, jest rozszerzenie za Optionalpomocą:

extension Optional {

    func ifValue(_ valueHandler: (Wrapped) -> Void) {
        switch self {
        case .some(let wrapped): valueHandler(wrapped)
        default: break
        }
    }

}

ifValueotrzymuje zamknięcie i wywołuje je z wartością jako argumentem, gdy opcjonalny nie jest zerowy. Jest używany w ten sposób:

var helloString: String? = "Hello, World!"

helloString.ifValue {
    print($0) // prints "Hello, World!"
}

helloString = nil

helloString.ifValue {
    print($0) // This code never runs
}

Prawdopodobnie powinieneś użyć iflub guardjednak, ponieważ są to najbardziej konwencjonalne (a więc znane) podejścia używane przez programistów Swift.


2

Jedną z opcji, która nie została szczegółowo omówiona, jest użycie ignorowanej składni wartości Swift:

if let _ = xyz {
    // something that should only happen if xyz is not nil
}

Podoba mi się to, ponieważ sprawdzanie nilczuje się nie na miejscu w nowoczesnym języku, takim jak Swift. Myślę, że powodem, dla którego wydaje się to nie na miejscu, niljest w zasadzie wartość wartownicza. Zrezygnowaliśmy z wartowników praktycznie wszędzie indziej we współczesnym programowaniu, więc nilwydaje się, że to też powinno odejść.


1

Możesz również użyć Nil-Coalescing Operator

nil-coalescing operator( a ?? b) Rozpakowuje opcjonalny ajeśli zawiera awartość lub zwrotu awartości domyślnej b, jeśli ajest nil. Wyrażenie a ma zawsze typ opcjonalny. Wyrażenie bmusi pasować do typu, który jest przechowywany w środku a.

let value = optionalValue ?? defaultValue

Jeśli optionalValuetak nil, automatycznie przypisuje wartośćdefaultValue


Podoba mi się to, ponieważ daje łatwą opcję określenia wartości domyślnej.
thowa

0
var xyz : NSDictionary?

// case 1:
xyz = ["1":"one"]
// case 2: (empty dictionary)
xyz = NSDictionary() 
// case 3: do nothing

if xyz { NSLog("xyz is not nil.") }
else   { NSLog("xyz is nil.")     }

Ten test działał zgodnie z oczekiwaniami we wszystkich przypadkach. BTW, nie potrzebujesz nawiasów ().


2
Sprawa PO jest zaniepokojony jest: if (xyz != nil).
zaph

0

Jeśli masz warunkowe i chciałbyś rozpakować i porównać, co powiesz na skorzystanie z oceny zwarcia złożonego wyrażenia logicznego, jak w

if xyz != nil && xyz! == "some non-nil value" {

}

To prawda, nie jest to tak czytelne, jak niektóre inne sugerowane posty, ale wykonuje zadanie i jest nieco zwięzłe niż inne sugerowane rozwiązania.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.