Sprawdzanie wartości opcjonalnej wartości logicznej

88

Kiedy chcę sprawdzić, czy opcjonalna wartość logiczna jest prawdziwa, wykonanie tego nie działa:

var boolean : Bool? = false
if boolean{
}

Powoduje to ten błąd:

Opcjonalny typ „@IvalueBool?” nie może być używany jako wartość logiczna; zamiast tego przetestuj '! = nil'

Nie chcę sprawdzać, czy jest zero; Chcę sprawdzić, czy zwrócona wartość jest prawdziwa.

Czy zawsze muszę to robić, if boolean == truejeśli pracuję z opcjonalnym boolem?

Skoro opcje nie są BooleanTypejuż zgodne z , czy kompilator nie powinien wiedzieć, że chcę sprawdzić wartość Bool?

Moon Cat
źródło
Ponieważ wartości logiczne są zgodne z protokołem Equatable, możesz porównać opcjonalne z nieopcjonalnym. Zobacz tutaj
Honey

Odpowiedzi:

192

W przypadku opcjonalnych wartości logicznych konieczne jest, aby czek był jawny:

if boolean == true {
    ...
}

W przeciwnym razie możesz rozpakować opcjonalne:

if boolean! {
    ...
}

Ale to generuje wyjątek czasu wykonywania, jeśli wartość logiczna jest nil- aby temu zapobiec:

if boolean != nil && boolean! {
    ...
}

Przed wersją beta 5 było to możliwe, ale zostało zmienione zgodnie z informacją o wydaniu:

Opcje nie są już niejawnie obliczane na true, gdy mają wartość i false, gdy nie mają, aby uniknąć nieporozumień podczas pracy z opcjonalnymi wartościami Bool. Zamiast tego dokonaj jawnego sprawdzenia względem nil za pomocą operatorów == lub! =, Aby dowiedzieć się, czy opcja opcjonalna zawiera wartość.

Dodatek: zgodnie z sugestią @MartinR, bardziej zwarta odmiana trzeciej opcji wykorzystuje operator koalescencji:

if boolean ?? false {
    // this code runs only if boolean == true
}

co oznacza: jeśli wartość logiczna nie jest równa nil, wynikiem wyrażenia jest wartość logiczna (tj. przy użyciu nieopakowanej wartości logicznej), w przeciwnym razie wynikiem wyrażenia jest false

Antonio
źródło
4
Trzecia opcja jest preferowanym rozwiązaniem, ponieważ jest najlepszym sposobem wyrażenia intencji kodu. Używanie if letteż by działało.
Sulthan
29
Wariant trzeciej opcji, wykorzystujący „operator koalescencji zerowej ??”: if boolean ?? false { ... } .
Martin R
4
Ale jeśli chcę temu zaprzeczać, zaczyna wyglądać śmiesznie: if !(boolean ?? true) { ... }:(
Andreas
2
Wymuszone rozpakowanie to skrajnie zły pomysł. Zawsze należy tego unikać.
Matthieu Riegler
4
Co jest nie tak z pierwszą opcją? Wydaje mi się, że to najlepszy sposób.
Vahid Amiri
43

Opcjonalne wiązanie

Swift 3 i 4

var booleanValue : Bool? = false
if let booleanValue = booleanValue, booleanValue {
    // Executes when booleanValue is not nil and true
    // A new constant "booleanValue: Bool" is defined and set
    print("bound booleanValue: '\(booleanValue)'")
}

Swift 2.2

var booleanValue : Bool? = false
if let booleanValue = booleanValue where booleanValue {
    // Executes when booleanValue is not nil and true
    // A new constant "booleanValue: Bool" is defined and set
    print("bound booleanValue: '\(booleanValue)'")
}

Kod let booleanValue = booleanValuezwraca falseifbooleanValue jest, nila ifblok nie jest wykonywany. Jeśli booleanValuenie nil, ten kod definiuje nową zmienną o nazwie booleanValuetype Bool(zamiast opcjonalnej Bool?).

Kod Swift 3 i 4 booleanValue(oraz kod Swift 2.2 where booleanValue) ocenia nową booleanValue: Boolzmienną. Jeśli to prawda, ifblok jest wykonywany z nowo zdefiniowaną booleanValue: Boolzmienną w zakresie (umożliwiając opcję ponownego odniesienia do wartości związanej w ifbloku).

Uwaga: Konwencją języka Swift jest nazwanie powiązanej stałej / zmiennej tak samo, jak opcjonalnej stałej / zmiennej, takiej jak let booleanValue = booleanValue . Ta technika nazywa się zmiennym cieniowaniem . Możesz zerwać z konwencją i użyć czegoś w rodzaju let unwrappedBooleanValue = booleanValue, unwrappedBooleanValue. Wskazuję na to, aby pomóc zrozumieć, co się dzieje. Polecam używanie zmiennego cieniowania.

 

Inne podejścia

Brak koalescencji

W tym konkretnym przypadku wyraźna jest koalescencja zerowa

var booleanValue : Bool? = false
if booleanValue ?? false {
    // executes when booleanValue is true
    print("optional booleanValue: '\(booleanValue)'")
}

Sprawdzanie dla false nie jest tak jasne

var booleanValue : Bool? = false
if !(booleanValue ?? false) {
    // executes when booleanValue is false
    print("optional booleanValue: '\(booleanValue)'")
}

Uwaga: if !booleanValue ?? false nie kompiluje się.

 

Wymuś rozpakowywanie opcjonalne (unikaj)

Wymuszanie rozpakowywania zwiększa szansę, że ktoś w przyszłości dokona zmiany, która kompiluje się, ale ulega awarii w czasie wykonywania. Dlatego unikałbym czegoś takiego:

var booleanValue : Bool? = false
if booleanValue != nil && booleanValue! {
    // executes when booleanValue is true
    print("optional booleanValue: '\(booleanValue)'")
}

 

Podejście ogólne

Chociaż to pytanie o przepełnienie stosu pyta konkretnie, jak sprawdzić, czy a Bool?znajduje się truewif instrukcji, pomocne jest określenie ogólnego podejścia, czy sprawdzanie wartości prawda, fałsz czy łączenie nieopakowanej wartości z innymi wyrażeniami.

Ponieważ wyrażenie staje się bardziej skomplikowane, uważam, że opcjonalne podejście wiążące jest bardziej elastyczne i łatwiejsze do zrozumienia niż inne podejścia. Należy zauważyć, że ewentualne prace związaniu z dowolnego typu (opcjonalnie Int?, String?itp).

Mobile Dan
źródło
Mam trudności z używaniem wyrażeń boolowskich z opcjonalnymi pętlami for. Operator koalescencji zerowej działa, ale jest bałaganiarski i podatny na błędy. Czy jest sposób na użycie if let?
jbaraga
@jbaraga, opublikuj przykład pętli while, nad którą się zastanawiasz.
Mobile Dan
Używając tablicy jako stosu, chcę zdejmować wartości, dopóki warunek nie zostanie spełniony lub stos jest pusty. Na przykładwhile array.last < threshold { array.removeLast() }
jbaraga
Możesz wykonać to przetwarzanie stosu, if, let, whereużywając tego: while let last = array.last where last < threshold { array.removeLast() }w Swift 2 lub while let last = array.last, last < threshold { array.removeLast() }Swift 3.
Mobile Dan
Tak jest lepiej, dzięki. Nie byłam tego świadoma while let.
jbaraga
1
var enabled: Bool? = true

if let enabled = enabled, enabled == true {
    print("when is defined and true at the same moment")
}

if enabled ?? false {
    print("when is defined and true at the same moment")
}

if enabled == .some(true) {
    print("when is defined and true at the same moment")
}

if enabled == (true) {
    print("when is defined and true at the same moment")
}

if case .some(true) = enabled {
    print("when is defined and true at the same moment")
}

if enabled == .some(false) {
    print("when is defined and false at the same moment")
}

if enabled == (false) {
    print("when is defined and false at the same moment")
}

if enabled == .none {
    print("when is not defined")
}

if enabled == nil {
    print("when is not defined")
}
Błażej SLEBODA
źródło
0

Znalazłem inne rozwiązanie, przeciążające operatory boolowskie. Na przykład:

public func < <T: Comparable> (left: T?, right: T) -> Bool {
    if let left = left {
        return left < right
    }
    return false
}

Może to nie być całkowicie zgodne z „duchem” zmian językowych, ale pozwala na bezpieczne rozpakowywanie opcji i jest możliwe do użycia w przypadku warunków warunkowych w dowolnym miejscu, w tym w pętlach while.

jbaraga
źródło
1
Przepraszam, patrząc wstecz na oryginalny post, nie odpowiada on na to konkretne pytanie, ale raczej na pytanie, które zadałem w moim wcześniejszym komentarzu.
jbaraga
Byłbym bardzo ostrożny przy używaniu tego przeciążenia, ponieważ mogą zdarzyć się przypadki, w których nie chcesz, aby zero było traktowane jako wartość "większa niż" wartość różna od zera (możesz chcieć odwrotnego wyniku w pewnych kontekstach lub ewentualnie obsługa w całości). Używanie normalnego rozpakowywania zamiast tego zmusza cię do jawnego zajęcia się tym, jak chcesz obsłużyć wartości zerowe w każdym przypadku, więc mniej prawdopodobne jest, że napotkasz nieoczekiwane wyniki.
John Montgomery
0

Najłatwiejszą do odczytania odpowiedzią jest zdefiniowanie funkcji. Niezbyt skomplikowane, ale działa.

func isTrue(_ bool: Bool?) -> Bool {
    guard let b = bool else {
        return false
    }
    return b
}

stosowanie:

let b: Bool? = true
if isTrue(b) {
    // b exists and is true
} else {
    // b does either not exist or is false
}
Charel ctk
źródło
0

Jak powiedział Antonio

Opcje nie są już niejawnie obliczane na true, gdy mają wartość i false, gdy nie mają, aby uniknąć nieporozumień podczas pracy z opcjonalnymi wartościami Bool. Zamiast tego dokonaj jawnego sprawdzenia względem nil za pomocą operatorów == lub! =, Aby dowiedzieć się, czy opcja opcjonalna zawiera wartość.

Spędziłem kilka godzin próbując zrozumieć wiersz kodu, na który się natknąłem, ale ten wątek postawił mnie na właściwej drodze.

Ten cytat pochodzi z sierpnia 2014 r. , A od tego czasu Apple przedstawił Nevernastępującą propozycję SE-0102, a następnie dostosował ją do Equatable, Hashable, Error i Comparable

Teraz można sprawdzić, czy boolean nilużywa Never?:


var boolean: Bool? = false
boolean is Never? // false
boolean = true
boolean is Never? // false
boolean = nil
boolean is Never? // true

W rzeczywistości możesz użyć innych typów nienadających się do zamieszkania :

public enum NeverEver { }
var boolean: Bool? = false
boolean is NeverEver? // false
boolean = true
boolean is NeverEver? // false
boolean = nil
boolean is NeverEver? // true

Biorąc to pod uwagę, można teraz również użyć opakowania właściwości :

@propertyWrapper struct OptionalBool {
    public var wrappedValue: Bool?
    public var projectedValue: Bool { wrappedValue ?? false }
    public init(wrappedValue: Bool?) {
        self.wrappedValue = wrappedValue
    }
}

struct Struct {
    @OptionalBool var predicate: Bool?
    var description: String {
        if $predicate {
            return "predicate is true"
        }
        return "predicate is false"
    }
}

var object = Struct()
object.description // "predicate is false"
object.predicate = false
object.description // "predicate is false"
object.predicate = true
object.description // "predicate is true"

lub nawet:

@propertyWrapper struct OptionalBool {
    var wrappedValue: Bool?
    var projectedValue: OptionalBool { self }
    var isNil: Bool { wrappedValue is Never? }
    var value: Bool { wrappedValue ?? false }
    
    init(wrappedValue: Bool?) {
        self.wrappedValue = wrappedValue
    }
}

struct Struct {
    @OptionalBool var predicate: Bool?
    var description: String {
        if $predicate.value {
            return "predicate is true"
        }
        if !$predicate.isNil {
            return "predicate is false"
        }
        return "predicate is nil"
    }
}

var object = Struct()
object.description // "predicate is nil"
object.predicate = false
object.description // "predicate is false"
object.predicate = true
object.description // "predicate is true"

AnderCover
źródło