Swift: Тестування факультативів на нуль


137

Я використовую Xcode 6 Beta 4. У мене є така дивна ситуація, коли я не можу зрозуміти, як належним чином перевірити додаткові можливості.

Якщо у мене є необов'язковий xyz, це правильний спосіб перевірки:

if (xyz) // Do something

або

if (xyz != nil) // Do something

У документах кажуть зробити це першим способом, але я виявив, що іноді потрібен другий спосіб, і не створюється помилка компілятора, але в інших випадках другий спосіб генерує помилку компілятора.

Мій конкретний приклад - використання GData XML-аналізатора, з'єднаного мостом для швидкого переходу:

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`.
}

5
Хтось може детальніше пояснити, чому xyz == nil не працює?
Крістоф

Другий приклад повинен читати: // Зробіть щось, використовуючи xy (що є основною відмінністю випадків використання між двома варіантами)
Альпер

@Christoph: Повідомлення про помилку полягає в тому, що константа 'xyz' використовується до ініталізації. Я думаю, вам потрібно додати "= nil" в кінці рядка декларації xyz.
користувач3207158

Для тих, хто починає швидко, як і я, і цікаво: Ви все одно повинні розгорнути 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.


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це не нульове значення, якщо закриття буде виконано і воно x_valбуде доступне всередині. Інакше спрацьовує закриття.

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

}

2. охоронець хай

guard letможе робити подібні речі. Його головна мета - зробити логічно більш розумним. Це як сказати Переконайтеся, що змінна не є нульовою, інакше зупиніть функцію . guard letможе також зробити додаткову перевірку стану як if let.

Відмінності полягають у тому, що розгорнуте значення буде доступне в тому ж обсязі guard let, що і в коментарі нижче. Це також призводить до того , що в іншому закриття, програма повинна вийти з поточної області шляхом return, breakі т.д.

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

11

З швидкого посібника з програмування

Якщо заяви та примусове розгортання

Ви можете використовувати оператор if, щоб дізнатися, чи містить необов'язкове значення. Якщо необов'язково має значення, воно оцінюється як істинне; якщо воно взагалі не має значення, воно оцінюється як хибне.

Тож найкращий спосіб це зробити

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

і якщо ви використовуєте xyzоператор xyzin if.Так ви можете розгорнути, якщо оператор у постійній змінній. Отже, вам не потрібно розгортати кожне місце, якщо оператор, де xyzвикористовується.

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

Цю конвенцію пропонують appleі дотримуватимуться її розробники.


2
У Swift 3 ви повинні зробити if xyz != nil {...}.
psmythirl

У Swift 3 додаткові опції не можна використовувати як булеві. По-перше, оскільки це C-ізм, який ніколи не повинен був бути в мові, по-друге, тому що він викликає абсолютну плутанину з необов'язковим 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
як було сказано вище, ви також можете це зробити, щоб позбутися від вкладених необов'язкових статменет. якщо нехай abc = abc? .xyz? .something {}
Suhaib

1
@Suhaib Ах, правда: Ви також можете скористатися додатковим ланцюжком ( developer.apple.com/library/prerelease/content/documentation/… ) для швидкого доступу до вкладених властивостей. Я не згадував це тут, тому що думав, що це може бути занадто дотичною від початкового питання.
Кріс Фредерік

Чудова відповідь! Але Свіфт, OMG ... все ще довгий шлях від дружнього програміста!
turingtest

@turingested Дякую! На самом деле, я думаю , що це є програмістом людей в тому сенсі , що гарантує вам випадково не намагатися використовувати nilзначення, але я згоден , що крива навчання трохи крутий. :)
Кріс Фредерік

4

Розширення протоколу Swift 5

Ось підхід, що використовує розширення протоколу, щоб ви могли легко вбудувати необов'язковий нульовий чек:

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.
javadba

2

Тепер ви можете швидко зробити наступне, що дозволяє трохи відновити ціль-c if nil else

if textfieldDate.text?.isEmpty ?? true {

}

2

Замість цього if, потрійний оператор може стати в нагоді, коли ви хочете отримати значення на основі того, чи є щось нульове:

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

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.


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


Мені це подобається, тому що це дає простий варіант вказати значення за замовчуванням.
Тхова

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.")     }

Цей тест спрацював так, як очікувалося у всіх випадках. До речі, дужки вам не потрібні ().


2
Випадку OP стурбоване є: if (xyz != nil).
zaph

0

Якщо у вас є умовні і хотіли б розгортати та порівнювати, як щодо того, щоб скористатися оцінкою короткого замикання складної булевої експресії, як у

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

}

Зрозуміло, це не так читабельно, як деякі інші запропоновані посади, але виконує роботу і дещо лаконічніше, ніж інші запропоновані рішення.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.