Зниження опціонів у Swift: як? Введіть або як! Тип?


95

Враховуючи наступне у Swift:

var optionalString: String?
let dict = NSDictionary()

Яка практична різниця між наступними двома твердженнями:

optionalString = dict.objectForKey("SomeKey") as? String

проти

optionalString = dict.objectForKey("SomeKey") as! String?

1
Aslo Дивіться як! Оператор від Apple
Мед

Відповіді:


142

Практична різниця полягає в наступному:

var optionalString = dict["SomeKey"] as? String

optionalStringбуде змінною типу String?. Якщо базовий тип - це щось інше, ніж Stringце, то нешкідливо просто призначити nilнеобов'язковий.

var optionalString = dict["SomeKey"] as! String?

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

Перший стиль використовується if letдля безпечного розгортання необов’язкового:

if let string = dict["SomeKey"] as? String {
    // If I get here, I know that "SomeKey" is a valid key in the dictionary, I correctly
    // identified the type as String, and the value is now unwrapped and ready to use.  In
    // this case "string" has the type "String".
    print(string)
}

Хіба не перший спосіб завжди краще? Обидва повертають необов’язково тип String? Здається, ніби другий метод робить те саме, що і перший, але може зірватися, якщо зниження не буде успішним. То навіщо його взагалі використовувати?
Сікандер

6
Так @Sikander, перший завжди краще. Я б ніколи не користувався другим.
vacawama

14

as? Types- означає, що процес лиття вниз не є обов'язковим. Процес може бути успішним чи ні (система поверне нуль, якщо не вдається виконати лиття). Будь-який спосіб не вийде з ладу, якщо не вдається виконати лиття.

as! Type?- Тут процес лиття вниз повинен бути успішним (це !вказує). Кінцевий знак запитання вказує, чи може кінцевий результат бути нульовим чи ні.

Детальніше про "!" і "?"

Візьмемо 2 справи

  1. Поміркуйте:

    let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as? UITableViewCell

    Тут ми не знаємо, чи є результат перекидання комірки з ідентифікатором "Cell" до UITableViewCell успішним чи ні. Якщо це не вдалося, він повертає нуль (тому ми уникаємо аварій тут). Тут ми можемо зробити, як зазначено нижче.

    if let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as? UITableViewCell {
        // If we reached here it means the down casting was successful
    }
    else {
        // unsuccessful down casting
    }

    Тож давайте запам’ятаємо це так - якщо ?це означає, що ми не впевнені, чи є значення нульовим чи ні (знак запитання з’являється, коли ми не знаємо речей).

  2. На противагу цьому:

    let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! UITableViewCell. 

    Тут ми повідомляємо компілятору, що кастинг вниз повинен бути успішним. Якщо це не вдасться, система вийде з ладу. Тому ми даємо, !коли ми впевнені, що значення не нульове.


11

Щоб уточнити, що сказав вакавама, ось приклад ...

Swift 3.0:

import UIKit

let str_value:    Any   = String("abc")!
let strOpt_value: Any?  = String("abc")!
let strOpt_nil:   Any?  = (nil as String?)
let int_value:    Any   = Int(1)
let intOpt_value: Any?  = Int(1)
let intOpt_nil:   Any?  = (nil as Int?)

// as String
//str_value     as String // Compile-Time Error: 'Any' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//strOpt_value  as String // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//strOpt_nil    as String // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//int_value     as String // Compile-Time Error: 'Any' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//intOpt_value  as String // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//intOpt_nil    as String // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?

// as? String
  str_value     as? String // == "abc"
  strOpt_value  as? String // == "abc"
  strOpt_nil    as? String // == nil
  int_value     as? String // == nil
  intOpt_value  as? String // == nil
  intOpt_nil    as? String // == nil

// as! String
  str_value     as! String // == "abc"
  strOpt_value  as! String // == "abc"
//strOpt_nil    as! String // Run-Time Error: unexpectedly found nil while unwrapping an Optional value.
//int_value     as! String // Run-Time Error: Could not cast value of type 'Swift.Int' to 'Swift.String'.
//intOpt_value  as! String // Run-Time Error: Could not cast value of type 'Swift.Int' to 'Swift.String'.
//intOpt_nil    as! String // Run-Time Error: unexpectedly found nil while unwrapping an Optional value.

// as String?
//str_value     as String? // Compile-Time Error: cannot convert value of type 'Any' to type 'String?' in coercion
//strOpt_value  as String? // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//strOpt_nil    as String? // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//int_value     as String? // Compile-Time Error: cannot convert value of type 'Any' to type 'String?' in coercion
//intOpt_value  as String? // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//intOpt_nil    as String? // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?

// as? String?
//str_value     as? String? // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  strOpt_value  as? String? // == "abc"
  strOpt_nil    as? String? // == nil
//int_value     as? String? // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  intOpt_value  as? String? // == nil
  intOpt_nil    as? String? // == nil

// as! String?
//str_value     as! String? // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  strOpt_value  as! String? // == "abc"
  strOpt_nil    as! String? // == nil
//int_value     as! String? // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
//intOpt_value  as! String? // Run-Time Error: Could not cast value of type 'Swift.Int' to 'Swift.String'.
  intOpt_nil    as! String? // == nil

// let _ = ... as String
//if let _ = str_value    as String { true } // Compile-Time Error: 'Any' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//if let _ = strOpt_value as String { true } // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//if let _ = strOpt_nil   as String { true } // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//if let _ = int_value    as String { true } // Compile-Time Error: 'Any' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//if let _ = intOpt_value as String { true } // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?
//if let _ = intOpt_nil   as String { true } // Compile-Time Error: 'Any?' is not convertible to 'String'; did you mean to use 'as!' to force downcast?

// let _ = ... as? String
if let _ = str_value    as? String { true } // true
if let _ = strOpt_value as? String { true } // true
if let _ = strOpt_nil   as? String { true } // false
if let _ = int_value    as? String { true } // false
if let _ = intOpt_value as? String { true } // false
if let _ = intOpt_nil   as? String { true } // false

// let _ = ... as! String
//if let _ = str_value    as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'
//if let _ = strOpt_value as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'
//if let _ = strOpt_nil   as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'
//if let _ = int_value    as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'
//if let _ = intOpt_value as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'
//if let _ = intOpt_nil   as! String { true } // Compile-Time Error: initializer for conditional binding must have Optional type, not 'String'

// let _ = ... as String?
//if let _ = str_value    as String? { true } // Compile-Time Error: cannot convert value of type 'Any' to type 'String?' in coercion
//if let _ = strOpt_value as String? { true } // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//if let _ = strOpt_nil   as String? { true } // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//if let _ = int_value    as String? { true } // Compile-Time Error: cannot convert value of type 'Any' to type 'String?' in coercion
//if let _ = intOpt_value as String? { true } // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?
//if let _ = intOpt_nil   as String? { true } // Compile-Time Error: 'Any?' is not convertible to 'String?'; did you mean to use 'as!' to force downcast?

// let _ = ... as? String?
//if let _ = str_value    as? String? { true } // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  if let _ = strOpt_value as? String? { true } // true
  if let _ = strOpt_nil   as? String? { true } // true
//if let _ = int_value    as? String? { true } // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  if let _ = intOpt_value as? String? { true } // false
  if let _ = intOpt_nil   as? String? { true } // true

// let _ = ... as! String?
//if let _ = str_value    as! String? { true } // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
  if let _ = strOpt_value as! String? { true } // true
  if let _ = strOpt_nil   as! String? { true } // false
//if let _ = int_value    as! String? { true } // Compile-Time Error: cannot downcast from 'Any' to a more optional type 'String?'
//if let _ = intOpt_value as! String? { true } // Run-Time Error: Could not cast value of type 'Swift.Int' to 'Swift.String'.
  if let _ = intOpt_nil   as! String? { true } // false

Swift 2.0:

import UIKit

let str:    AnyObject   = String("abc")
let strOpt: AnyObject?  = String("abc")
let strNil: AnyObject?  = (nil as String?)
let int:    AnyObject   = Int(1)
let intOpt: AnyObject?  = Int(1)
let intNil: AnyObject?  = (nil as Int?)

str    as? String // == "abc"
strOpt as? String // == "abc"
strNil as? String // == nil
int    as? String // == nil
intOpt as? String // == nil
intNil as? String // == nil

str    as! String? // Compile-Time Error: Cannot downcast from 'AnyObject' to a more optional type 'String?'
strOpt as! String? // == "abc"
strNil as! String? // == nil
int    as! String? // Compile-Time Error: Cannot downcast from 'AnyObject' to a more optional type 'String?'
intOpt as! String? // Run-Time Error: Could not cast value of type '__NSCFNumber' to 'NSString'
intNil as! String? // == nil

+1 для вашого прикладу, але чи можете ви пояснити мені тим самим прикладом, як і! замість як? в той час як downcasting як cell = tableView.dequeueReusableCellWithIdentifier ("Cell") як! UITableViewCell .. я здогадуюсь як? було достатньо, чому була потреба як!
Аніш Параджулі 웃

нехай cell = tableView.dequeueReusableCellWithIdentifier ("Cell") як? UITableViewCell. - тут ми не знаємо, чи є результат відкидання комірки з ідентифікатором "Cell" на UITableViewCell нульовим чи ні. Якщо nill, то він повертає nill (тому ми уникнемо збою тут).
jishnu bala

цікаво, intNil as! String? // ==nilне спричиняє аварії !!! ???, як необов’язково <Int> .Ніхто не відрізняється від необов’язкового <String> .None
onmyway133

чому ви опущений as?до String? Чому б вам не припустити це String?? Чому ви не опущеними as!до String?
Мед

Спроба зробити цей майданчик у Swift 3, але вам доведеться використовувати AnyзамістьAnyObject
Honey

9
  • as використовується для оновлення та виливання типів на мостовий тип
  • as? використовується для безпечного лиття, поверніть нуль, якщо не вдалося
  • as! використовується для примусового лиття, аварії, якщо не вдалося

Примітка:

  • as! не можна передати необроблений тип на необов’язковий

Приклади:

let rawString: AnyObject = "I love swift"
let optionalString: AnyObject? = "we love swift"
let nilString: AnyObject? = (nil as String?)

let rawInt: AnyObject = Int(3)
let optionalInt: AnyObject? = Int(3)
let nilInt: AnyObject? = (nil as Int?)

Приклад

var age: Int? = nil
var height: Int? = 180

Додавши ? одразу після типу даних ви повідомляєте компілятору, що змінна може містити число чи ні. Акуратно! Зауважте, що визначати необов'язкові константи не має сенсу - ви можете встановити їх значення лише один раз, і тому ви зможете сказати, буде їх значення нульовим чи ні.

Коли нам слід використовувати "?" і коли "!"

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

Як ми знаємо, кожен екземпляр ViewController має контролер навігації властивостей. Якщо ви створюєте додаток на базі контролера навігації, це властивість контролера головного перегляду додатка встановлюється автоматично, і ви можете використовувати його для натискання або виведення контролерів перегляду. Якщо ви використовуєте єдиний шаблон проекту програми - для вас не буде створено автоматичний контролер навігації, тож у контролері подання програми за замовчуванням не буде збережено нічого у властивості navigationController.

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

var navigationController: UINavigationController? { get }

Тож повернемося до нашого випадку використання. Якщо ви знаєте, що ваш контролер перегляду завжди матиме навігаційний контролер, ви можете продовжити його і змусити його розгортати:

controller.navigationController!.pushViewController(myViewController, animated: true)

Коли ви ставите! за назвою властивості ви говорите компілятору, що мені все одно, що ця властивість є необов’язковою, я знаю, що при виконанні цього коду завжди буде зберігати значення, тож ставіться до цього необов’язкового як до звичайного типу даних. Ну хіба це не приємно? Що може статися, якщо у вашому контролері перегляду немає контролера навігації? Якщо ви припускаєте, що в навігаційному контролері завжди знайдеться значення, було неправильним? Ваш додаток вийде з ладу. Простий і некрасивий.

Отже, використовуйте! тільки якщо ви на 101% впевнені, що це безпечно.

А як бути, якщо ви не впевнені, що завжди буде контролер навігації? Тоді ви можете використовувати? замість!

controller.navigationController?.pushViewController(myViewController, animated: true)

Що за ? за ім'ям властивості вказано компілятору: я не знаю, чи містить ця властивість нуль або значення, тому: якщо у неї є значення, використовуйте його, і в іншому випадку просто врахуйте весь вираз нуль. Ефективно? дозволяє використовувати цю властивість лише у випадку, якщо є навігаційний контролер. Ні, якщо перевірки будь-якого виду або кастингу будь-якого типу. Цей синтаксис є ідеальним, коли вам не байдуже, чи є у вас навігаційний контролер чи ні, і хочете щось зробити лише за наявності.

Величезне завдяки Fantageek


8

Це дві різні форми Downcasting у Свіфті.

( as?) , яка, як відомо, є умовною формою , повертає необов'язкове значення типу, до якого ви намагаєтесь скинути.

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


( as!) , який, як відомо, є форсованою формою , намагається знищити і примусово розгортає результат у вигляді єдиної складової дії.

Ви повинні використовувати його ТІЛЬКИ коли ви впевнені, що зниження завжди буде успішним. Ця форма оператора спровокує помилку виконання, якщо ви спробуєте перейти до невірного типу класу.

Щоб отримати докладнішу інформацію, перегляньте розділ Type Casting в документації Apple.


4

Можливо, цей приклад коду допоможе комусь подолати принцип:

var dict = [Int:Any]()
dict[1] = 15

let x = dict[1] as? String
print(x) // nil because dict[1] is an Int

dict[2] = "Yo"

let z = dict[2] as! String?
print(z) // optional("Yo")
let zz = dict[1] as! String // crashes because a forced downcast fails


let m = dict[3] as! String?
print(m) // nil. the forced downcast succeeds, but dict[3] has no value

Крім того, нехай z2 = dict [2] як! Рядок // "Йо" (не є обов'язковим)
Джей

0

Перший - це "умовний склад" (дивіться розділ "Оператори лиття типу" в документації, яку я зв'язав) . Якщо виклик вдався, значення виразу загортається у необов'язковий та повертається, інакше повернене значення дорівнює нулю.

Другий означає, що optionalString може бути рядковим об'єктом, а може бути і нульовим.

Більше інформації ви знайдете у цьому суміжному питанні .


0

Можливо, найпростіше запам’ятати шаблон для цих операторів у Swift як: !передбачає "це може затримати", тоді як ?вказує "це може бути нулем".

зверніться до: https://developer.apple.com/swift/blog/?id=23


-1

Я новачок у Swift і пишу цей приклад, намагаючись пояснити, як я розумію, про "необов'язкові". Якщо я помиляюся, будь ласка, виправте мене.

Дякую.


class Optional {

    var lName:AnyObject! = "1"

    var lastName:String!
}

let obj = Optional()

print(obj.lName)

print(obj.lName!)

obj.lastName = obj.lName as? String

print(obj.lastName)

(1): obj.lastName = obj.lName as! String

проти

(2): obj.lastName = obj.lName as? String

Відповідь: (1) Тут програміст впевнений, що “obj.lName”містить об’єкт типу рядка. Тож просто надайте цьому значення “obj.lastName”.

Тепер, якщо програміст правильний, означає, що "obj.lName"це об’єкт типу рядка, то не проблема. "obj.lastName" встановить те саме значення.

Але якщо програміст помиляється, значить, "obj.lName"це не об'єкт типу рядка, тобто він містить якийсь інший тип об'єкта, наприклад "NSNumber" і т.д.

(2) Програміст не впевнений, що “obj.lName”містить об’єкт типу рядка або будь-який інший об’єкт типу. Тому встановіть це значення, “obj.lastName”якщо воно є рядковим типом.

Тепер, якщо програміст правильний, означає, що “obj.lName”це об’єкт типу рядка, то не проблема. “obj.lastName”буде встановлено однакове значення.

Але якщо програміст неправильно кошти obj.lName не об'єкт типу рядок , тобто містить будь - якої іншої об'єкт типу , як і "NSNumber"т.д. Тоді “obj.lastName”буде присвоєно значення нуль. Отже, No Crash (Happy :)

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