Verificando o valor de um Bool opcional

88

Quando desejo verificar se um Bool opcional é verdadeiro, isso não funciona:

var boolean : Bool? = false
if boolean{
}

Isso resulta neste erro:

Tipo opcional '@IvalueBool?' não pode ser usado como booleano; teste para '! = nil' em vez disso

Não quero verificar se há zero; Quero verificar se o valor retornado é verdadeiro.

Sempre preciso fazer isso if boolean == truese estiver trabalhando com um Bool opcional?

Visto que os opcionais não estão BooleanTypemais em conformidade com o , o compilador não deveria saber que eu quero verificar o valor do Bool?

Gato da lua
fonte
Como os booleanos estão em conformidade com o protocolo Equatable, você pode comparar um opcional com um não opcional. Veja aqui
Honey

Respostas:

192

Com booleanos opcionais, é necessário tornar a verificação explícita:

if boolean == true {
    ...
}

Caso contrário, você pode desembrulhar o opcional:

if boolean! {
    ...
}

Mas isso gera uma exceção de tempo de execução se booleano for nil- para evitar que:

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

Antes do beta 5 era possível, mas foi alterado conforme relatado nas notas de lançamento:

Os opcionais não são mais avaliados implicitamente como verdadeiro quando têm um valor e como falso quando não têm, para evitar confusão ao trabalhar com valores Bool opcionais. Em vez disso, faça uma verificação explícita contra nil com os operadores == ou! = Para descobrir se um opcional contém um valor.

Adendo: conforme sugerido por @MartinR, uma variação mais compacta para a 3ª opção é usar o operador de coalescência:

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

o que significa: se booleano não for nulo, a expressão avalia o valor booleano (ou seja, usando o valor booleano não agrupado), caso contrário, a expressão avalia para false

Antonio
fonte
4
A terceira opção é a solução preferida porque é a melhor maneira de expressar a intenção do código. Usar if lettambém funcionaria.
Sulthan
29
Uma variante da terceira opção, usando o "operador coalescentes nulo ??": if boolean ?? false { ... } .
Martin R
4
Mas se eu quiser negar, começa a parecer ridículo: if !(boolean ?? true) { ... }:(
Andreas
2
Forçado a desembrulhar uma péssima ideia. Deve sempre ser evitado.
Matthieu Riegler
4
O que há de errado com a primeira opção? Para mim parece o melhor caminho.
Vahid Amiri
43

Ligação opcional

Swift 3 e 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)'")
}

O código let booleanValue = booleanValueretorna falsese booleanValuefor nile o ifbloco não executa. Se booleanValuenão for nil, este código define uma nova variável chamada booleanValuede tipo Bool(em vez de opcional, Bool?).

O código Swift 3 e 4 booleanValue(e código Swift 2.2 where booleanValue) avalia a nova booleanValue: Boolvariável. Se for verdade, o ifbloco é executado com a booleanValue: Boolvariável recém-definida no escopo (permitindo a opção de referenciar o valor vinculado novamente dentro do ifbloco).

Nota: É uma convenção do Swift nomear a constante / variável associada da mesma forma que a constante / variável opcional, como let booleanValue = booleanValue. Essa técnica é chamada de sombreamento variável . Você pode quebrar as convenções e usar algo assim let unwrappedBooleanValue = booleanValue, unwrappedBooleanValue. Eu aponto isso para ajudar a entender o que está acontecendo. Eu recomendo usar sombreamento variável.

 

Outras Abordagens

Nil coalescendo

A coalescência nula é clara para este caso específico

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

Verificar falsenão é tão claro

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

Nota: if !booleanValue ?? falsenão compila.

 

Forçar desembrulhar opcional (evitar)

Forçar o desempacotamento aumenta a chance de que alguém faça uma alteração no futuro que compila, mas falha durante a execução. Portanto, eu evitaria algo assim:

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

 

Uma abordagem geral

Embora esta questão de estouro de pilha pergunte especificamente como verificar se a Bool?está truedentro de uma ifinstrução, é útil identificar uma abordagem geral, seja verificando verdadeiro, falso ou combinando o valor não encapsulado com outras expressões.

À medida que a expressão fica mais complicada, acho a abordagem de vinculação opcional mais flexível e fácil de entender do que outras abordagens. Nota que as obras de ligação opcionais com qualquer tipo opcional ( Int?, String?, etc.).

Dan móvel
fonte
Estou tendo dificuldade em usar expressões booleanas com loops while opcionais. O operador de coalescência nil funciona, mas é confuso e sujeito a erros. Existe uma maneira de usar if let?
jbaraga
@jbaraga, poste um exemplo do loop while sobre o qual você está se perguntando.
Mobile Dan
Ao usar uma matriz como uma pilha, desejo retirar os valores até que uma condição seja satisfeita ou a pilha esteja vazia. Por exemplo,while array.last < threshold { array.removeLast() }
jbaraga
Você pode realizar esse processamento de pilha if, let, whereusando este: while let last = array.last where last < threshold { array.removeLast() }no Swift 2 ou while let last = array.last, last < threshold { array.removeLast() }no Swift 3.
Mobile Dan
Assim está melhor, obrigado. Eu não estava ciente 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")
}
Blazej SLEBODA
fonte
0

Encontrei outra solução, sobrecarregando os operadores booleanos. Por exemplo:

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

Isso pode não estar totalmente no "espírito" das mudanças de linguagem, mas permite o desempacotamento seguro de opcionais e pode ser usado para condicionais em qualquer lugar, incluindo loops while.

Jbaraga
fonte
1
Desculpe, olhando para a postagem original, ela não responde a essa pergunta específica, mas sim à pergunta que levantei em meu comentário anterior.
jbaraga
Eu teria muito cuidado ao usar essa sobrecarga, porque pode haver casos em que você não deseja que nil seja tratado como "maior que" um valor não nulo (você pode querer o resultado oposto em certos contextos, ou possivelmente uma alternativa manipulação inteiramente). O uso do desempacotamento normal, em vez disso, força você a abordar explicitamente como deseja lidar com nils em todos os casos, portanto, é menos provável que você encontre resultados inesperados.
John Montgomery
0

A resposta que achei mais fácil de ler é definir uma função. Não é muito complicado, mas faz o trabalho.

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

uso:

let b: Bool? = true
if isTrue(b) {
    // b exists and is true
} else {
    // b does either not exist or is false
}
Charel ctk
fonte
0

Como disse o Antonio

Os opcionais não são mais avaliados implicitamente como verdadeiro quando têm um valor e como falso quando não têm, para evitar confusão ao trabalhar com valores Bool opcionais. Em vez disso, faça uma verificação explícita contra nil com os operadores == ou! = Para descobrir se um opcional contém um valor.

Passei algumas horas tentando entender uma linha de código que encontrei, mas essa discussão me colocou no caminho certo.

Esta citação é de agosto 2014 , e desde então a Apple introduziu Neverseguinte proposta SE-0102 e este último tornou conformidade com Equatable, Hashable, erro e comparáveis

Agora é possível verificar se um booleano está nilusando Never?:


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

Na verdade, você pode usar qualquer outro tipo inabitável :

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

Dito isso, também é possível usar um wrapper de propriedade agora:

@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"

ou mesmo:

@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
fonte