Swift: тестирование опций на ноль

137

Я использую Xcode 6 Beta 4. У меня странная ситуация, когда я не могу понять, как правильно протестировать дополнительные компоненты.

Если у меня есть дополнительный xyz, это правильный способ проверки:

if (xyz) // Do something

или

if (xyz != nil) // Do something

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

Мой конкретный пример - использование синтаксического анализатора GData XML, соединенного с помощью swift:

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

if (xmlError != nil)

Вот если бы я только что сделал:

if xmlError

это всегда вернет истину. Однако, если я сделаю:

if (xmlError != nil)

тогда это работает (как, как это работает в Objective-C).

Есть ли что-то с GData XML и тем, как он обрабатывает необязательные параметры, которые мне не хватает?

тнг
источник
4
Можем ли мы увидеть полный пример для ваших непредвиденных случаев и ошибок, пожалуйста? (И я знаю, что это сложно, но постарайтесь начать терять скобки вокруг условных выражений!)
Мэтт Гибсон,
1
это изменено в Xcode 6 beta 5
newacct
Я только что обновил вопрос неожиданным случаем.
ПНП
Я еще не обновился до бета 5. Я сделаю это в ближайшее время; приятно видеть ?? в Swift, и более последовательное необязательное поведение.
ПНП

Ответы:

172

В Xcode Beta 5 они больше не позволяют вам:

var xyz : NSString?

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

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

не соответствует протоколу BooleanType.Protocol

Вы должны использовать одну из этих форм:

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

if let xy = xyz {
   // Do something using `xy`.
}
ktzhang
источник
5
Может кто-нибудь уточнить, почему xyz == nil не работает?
Кристоф
Второй пример должен выглядеть следующим образом: // Делать что-то, используя xy (что является основным различием в вариантах использования между двумя вариантами)
Alper
@Christoph: сообщение об ошибке состоит в том, что константа 'xyz' использовалась перед инициализацией. Я думаю, вам нужно добавить '= nil' в конце строки объявления xyz.
user3207158
Для тех, кто плохо знаком со Свифтом, как и я, и задается вопросом: вам все равно придется развернуть xyz внутри блока if. Это безопасно, даже если вы заставите развернуть
Омар
@ Omar В этом и заключается прелесть второй формы: вы используете xy внутри блока, не распаковывая его.
Эрик Доерненбург
24

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

var a: Int? = 5

if let b = a {
   // do something
}

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

var a: Int? = 5

if let a = a {
    // do something
}

Это может помочь вам избежать исчерпания творческих имен переменных ...

Это использует преимущество затенения переменных , которое поддерживается в Swift.

zevij
источник
18

Одним из наиболее прямых способов использования опциональных программ является следующее:

Предполагается, что xyzэто необязательный тип, как Int?например.

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

Таким образом, вы можете одновременно проверить, xyzсодержит ли значение, и если да, немедленно поработать с этим значением.

Что касается ошибки вашего компилятора, тип UInt8не является обязательным (обратите внимание, нет '?') И поэтому не может быть преобразован в nil. Убедитесь, что переменная, с которой вы работаете, является необязательной, прежде чем рассматривать ее как единое целое.

Исаак Драхман
источник
1
Я считаю этот метод плохим, потому что я создаю новую переменную (константу) без необходимости, и приходится создавать новое имя, которое в большинстве случаев будет хуже, чем предыдущее. Занимает больше времени, чтобы написать и для читателя.
Ломбас
3
Это стандартный и рекомендуемый метод для развертывания необязательной переменной. «если позволите» очень сильно.
gnasher729
@ gnasher729 но что, если вы хотите использовать только остальную часть
Брэд Томас
15

Swift 3.0, 4.0

Есть в основном два способа проверки необязательных значений на ноль. Вот примеры со сравнением между ними

1. если позволено

if letэто самый простой способ проверить опционально на ноль. К этой нулевой проверке могут быть добавлены другие условия, разделенные запятой. Переменная не должна быть равна нулю, чтобы перейти к следующему условию. Если требуется только проверка на ноль, удалите дополнительные условия в следующем коде.

Кроме этого, если xне ноль, то закрытие if будет выполнено и x_valбудет доступно внутри. В противном случае происходит закрытие else.

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

}

2. охранник пусть

guard letможет делать подобные вещи. Его главная цель - сделать это логически более разумным. Это все равно что сказать, что переменная не nil, иначе остановите функцию . guard letТакже можно выполнить дополнительную проверку состояния как if let.

Различия в том, что развернутое значение будет доступно в той же области guard let, что и показано в комментарии ниже. Это также приводит к тому , что в остальном закрытия, программа должна выйти из текущей области путем return, breakи т.д.

guard let x_val = x, x_val > 5 else {
    return
}
//x_val available on this scope
Fangming
источник
11

Из быстрого руководства по программированию

Если заявления и принудительное развертывание

Вы можете использовать оператор if, чтобы узнать, содержит ли необязательный параметр значение. Если необязательный параметр имеет значение, он оценивается как true; если оно вообще не имеет значения, оно оценивается как ложное.

Так что лучший способ сделать это

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

и если вы используете xyzоператор if if.Than, вы можете развернуть xyzоператор if в константной переменной. Так что вам не нужно развертывать каждое место в операторе if, где xyzон используется.

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

Это соглашение предложено, appleи за ним последуют разработчики.

codester
источник
2
В Swift 3 вы должны сделать if xyz != nil {...}.
psmythirl
В Swift 3 опции не могут быть использованы как логическое значение. Во-первых, потому что это C-ism, который никогда не должен был присутствовать в языке, во-вторых, потому что он вызывает абсолютную путаницу с необязательным Bool.
gnasher729
9

Несмотря на то, что вы все равно должны явно сравнивать nilнеобязательную привязку или использовать необязательную привязку для дополнительного извлечения ее значения (т. Е. Необязательные неявно не преобразуются в логические значения), стоит отметить, что Swift 2 добавил guardоператор, чтобы помочь избежать пирамиды гибели при работе с несколькими необязательными значениями.

Другими словами, ваши опции теперь включают явную проверку nil:

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

Опциональная привязка:

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

И guardзаявления:

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

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

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

Вы можете избежать этого вложения с guardзаявлениями:

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
как уже упоминалось выше, вы можете сделать это тоже, чтобы избавиться от вложенных необязательных statmenets. если позволить abc = abc? .xyz? .something {}
Сухайб
1
@Suhaib Ах, правда: вы также можете использовать необязательную цепочку ( developer.apple.com/library/prerelease/content/documentation/… ) для быстрого доступа к вложенным свойствам. Я не упомянул это здесь, потому что думал, что это может быть слишком много касательно исходного вопроса.
Крис Фредерик
Отличный ответ! Но Swift, OMG ... еще далеко от дружелюбного отношения к программистам!
тестирование turing
@turingtested Спасибо! На самом деле, я думаю , что это является программистом людей в том смысле , что гарантирует вам случайно не пытаться использовать nilзначение, но я согласен , что кривая обучения немного крутой. :)
Крис Фредерик
4

Расширение протокола Swift 5

Вот подход, использующий расширение протокола, так что вы можете легко встроить дополнительную проверку nil:

import Foundation

public extension Optional {

    var isNil: Bool {

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

        return true

    }

    var isSome: Bool {

        return !self.isNil

    }

}

использование

var myValue: String?

if myValue.isNil {
    // do something
}

if myValue.isSome {
    // do something
}
Броди Робертсон
источник
Я получил несколько отрицательных ответов на этот ответ и хотел бы знать, почему. По крайней мере, комментарий, если вы собираетесь понизить.
Броди Робертсон
1
Вероятно, это swiftаналог того, pythonistasчто пытается сохранить язык в какой-то форме питонской чистоты. Мой взять? Я просто поднял твой код в мой миксин Utils.
Джавадба
2

Теперь вы можете быстро выполнить следующее, что позволит вам немного восстановить цель. if nil else

if textfieldDate.text?.isEmpty ?? true {

}
Николас Манзини
источник
2

Вместо этого if, троичный оператор может пригодиться, если вы хотите получить значение, основанное на том, является ли что-то нулем:

func f(x: String?) -> String {
    return x == nil ? "empty" : "non-empty"
}
QED
источник
xyz == nilвыражение не работает, но x != nilработает. Не знаю почему.
Андрей
2

Другой подход, помимо использования ifили guardоператоров для выполнения необязательного связывания, заключается в расширении Optionalс помощью:

extension Optional {

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

}

ifValueполучает замыкание и вызывает его со значением в качестве аргумента, если необязательным является не ноль. Используется так:

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

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

helloString = nil

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

Вы, вероятно, должны использовать ifили, тем не guardменее, так как это наиболее традиционные (знакомые) подходы, используемые программистами Swift.

Tiago
источник
2

Одна из опций, которая не была специально рассмотрена, - это использование синтаксиса игнорируемых значений Swift:

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

Мне это нравится, потому что проверка nilчувствуется неуместно в современном языке, таком как Swift. Я думаю, что причина, по которой это кажется неуместным, заключается в том, что nilэто в основном дозорная ценность. Мы покончили с дозорными в современном программировании почти везде, поэтому нам nilкажется, что так должно быть.

Бен Лахман
источник
1

Также вы можете использовать Nil-Coalescing Operator

nil-coalescing operator( a ?? b) Разворачивает необязательным , aесли он содержит aзначение, или возвращает aзначение по умолчанию , bесли aэто nil. Выражение a всегда имеет необязательный тип. Выражение bдолжно соответствовать типу, который хранится внутри a.

let value = optionalValue ?? defaultValue

Если optionalValueесть nil, он автоматически присваивает значениеdefaultValue

yoAlex5
источник
Мне это нравится, потому что это дает возможность легко указать значение по умолчанию.
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.")     }

Этот тест работал, как и ожидалось, во всех случаях. Кстати, вам не нужны скобки ().

Mundi
источник
2
Случае OP обеспокоено является: if (xyz != nil).
Zaph
0

Если у вас есть условия, и вы хотели бы развернуть и сравнить, как насчет того, чтобы воспользоваться преимуществами оценки сложного логического выражения при коротком замыкании, как в

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

}

Конечно, это не так легко читается, как некоторые другие предлагаемые посты, но выполняет работу и несколько кратко, чем другие предлагаемые решения.

RT Денвер
источник