Проверка значения необязательного Bool

88

Когда я хочу проверить, истинно ли значение Optional Bool, это не сработает:

var boolean : Bool? = false
if boolean{
}

Это приводит к этой ошибке:

Необязательный тип @IvalueBool? не может использоваться как логическое; вместо этого проверьте '! = nil'

Я не хочу проверять ноль; Я хочу проверить, истинно ли возвращаемое значение.

Всегда ли я должен это делать, if boolean == trueесли я работаю с Optional Bool?

Поскольку Optionals больше не соответствуют BooleanType, не должен ли компилятор знать, что я хочу проверить значение Bool?

Лунный кот
источник
Поскольку логические значения соответствуют протоколу Equatable, вы можете сравнить необязательное с необязательным. Смотрите здесь
Honey

Ответы:

192

С необязательными логическими значениями необходимо сделать проверку явной:

if boolean == true {
    ...
}

В противном случае вы можете развернуть необязательный:

if boolean! {
    ...
}

Но это генерирует исключение времени выполнения, если логическое значение равно nil- чтобы предотвратить это:

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

До бета 5 это было возможно, но было изменено, как сообщается в примечаниях к выпуску:

Optionals больше не неявно оценивают значение true, если у них есть значение, и false, когда нет, чтобы избежать путаницы при работе с дополнительными значениями Bool. Вместо этого сделайте явную проверку на nil с помощью операторов == или! =, Чтобы узнать, содержит ли необязательный параметр значение.

Добавление: как предлагает @MartinR, более компактный вариант третьего варианта использует оператор объединения:

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

что означает: если логическое значение не равно нулю, выражение оценивается как логическое значение (т.е. с использованием развернутого логического значения), в противном случае выражение оценивается как false

Антонио
источник
4
Третий вариант является предпочтительным решением, потому что это лучший способ выразить намерение кода. Использование if letтакже будет работать.
Sulthan
29
Вариант третьего варианта, используя «нулевой оператор коалесцирующего ??»: if boolean ?? false { ... } .
Martin R
4
Но если я хочу это опровергнуть, это начинает выглядеть нелепо: if !(boolean ?? true) { ... }:(
Андреас
2
Принудительно развернуть крайне плохую идею. Этого всегда следует избегать.
Matthieu Riegler
4
Что не так с первым вариантом? Мне это кажется лучшим способом.
Вахид Амири
43

Дополнительная привязка

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

Код let booleanValue = booleanValueвозвращается, falseесли booleanValueесть, nilи ifблок не выполняется. Если booleanValueнет nil, этот код определяет новую переменную с именем booleanValueтипа Bool(вместо необязательного, Bool?).

Код Swift 3 и 4 booleanValue(и код Swift 2.2 where booleanValue) оценивает новую booleanValue: Boolпеременную. Если это правда, ifблок выполняется с вновь определенной booleanValue: Boolпеременной в области видимости (позволяя опции снова ссылаться на связанное значение внутри ifблока).

Примечание. В соответствии с соглашением Swift связанная константа / переменная называется так же, как необязательная константа / переменная, например let booleanValue = booleanValue. Этот прием называется переменным затенением . Вы можете отказаться от условностей и использовать что-то вроде let unwrappedBooleanValue = booleanValue, unwrappedBooleanValue. Я указываю на это, чтобы помочь понять, что происходит. Я рекомендую использовать переменное затенение.

 

Другие подходы

Нулевое слияние

Отсутствие коалесценции очевидно для этого конкретного случая

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

Проверка falseне так ясна

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

Примечание: if !booleanValue ?? falseне компилируется.

 

Принудительное разворачивание необязательно (избегать)

Принудительное развертывание увеличивает вероятность того, что в будущем кто-то внесет изменение, которое компилируется, но дает сбой во время выполнения. Поэтому я бы избегал такого:

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

 

Общий подход

Хотя этот вопрос о переполнении стека конкретно спрашивает, как проверить, находится ли a Bool?в trueпределахif заявления, полезно определить общий подход ли проверка на истинный, ложный или комбинируя развернутое значение с другими выражениями.

По мере усложнения выражения я считаю необязательный подход связывания более гибким и легким для понимания, чем другие подходы. Обратите внимание, что дополнительная привязка работает с любым дополнительным типом ( Int?, String?и т. Д.).

Мобильный Дэн
источник
Мне трудно использовать логические выражения с необязательными циклами while. Оператор объединения nil работает, но он беспорядочный и подвержен ошибкам. Есть способ использовать if let?
jbaraga
@jbaraga, опубликуйте, пожалуйста, пример интересующего вас цикла while.
Mobile Dan
При использовании массива в качестве стека я хочу извлекать значения до тех пор, пока не будет выполнено условие или пока стек не станет пустым. Например,while array.last < threshold { array.removeLast() }
jbaraga
Вы можете выполнить эту обработку стека с if, let, whereпомощью этого: while let last = array.last where last < threshold { array.removeLast() }в Swift 2 или while let last = array.last, last < threshold { array.removeLast() }в Swift 3.
Mobile Dan
Так лучше, спасибо. Я не знал об этом 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")
}
Блажей СЛЕБОДА
источник
0

Я нашел другое решение, перегрузив логические операторы. Например:

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

Возможно, это не полностью соответствует «духу» языковых изменений, но позволяет безопасно разворачивать опциональные элементы и может использоваться для условных выражений где угодно, включая циклы while.

Джбарага
источник
1
Извините, оглядываясь назад на исходный пост, он не отвечает на этот конкретный вопрос, а скорее на вопрос, который я поднял в своем предыдущем комментарии.
jbaraga
Я был бы очень осторожен при использовании этой перегрузки, потому что могут быть случаи, когда вы не хотите, чтобы nil рассматривался как "больше" значения, отличного от nil (вам может потребоваться противоположный результат в определенных контекстах или, возможно, альтернативный обработка целиком). Использование обычного развертывания вместо этого заставляет вас явно указывать, как вы хотите обрабатывать nils в каждом случае, поэтому вы с меньшей вероятностью столкнетесь с неожиданными результатами.
Джон Монтгомери
0

Ответ, который я нашел наиболее легким для чтения, - это определение функции. Не очень сложно, но работает.

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

Применение:

let b: Bool? = true
if isTrue(b) {
    // b exists and is true
} else {
    // b does either not exist or is false
}
Чарел ЦТК
источник
0

Как сказал Антонио

Optionals больше не неявно оценивают значение true, если у них есть значение, и false, когда нет, чтобы избежать путаницы при работе с дополнительными значениями Bool. Вместо этого сделайте явную проверку на nil с помощью операторов == или! =, Чтобы узнать, содержит ли необязательный параметр значение.

Я потратил несколько часов, пытаясь понять строку кода, на которую наткнулся, но эта ветка направила меня на правильный путь.

Это цитата из августа 2014 года , и с тех пор Apple представила Neverследующее предложение SE-0102, и последнее сделало его совместимым с Equatable, Hashable, Error и Comparable.

Теперь можно проверить , если булева это с nilпомощью Never?:


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

Фактически вы можете использовать любые другие непригодные для проживания типы:

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

При этом теперь также можно использовать оболочку свойств :

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

или даже:

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

АндерКовер
источник