Що означає знак оклику в мові Swift?


518

Посібник з мови програмування Swift має такий приклад:

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { println("\(name) is being deinitialized") }
}

class Apartment {
    let number: Int
    init(number: Int) { self.number = number }
    var tenant: Person?
    deinit { println("Apartment #\(number) is being deinitialized") }
}

var john: Person?
var number73: Apartment?

john = Person(name: "John Appleseed")
number73 = Apartment(number: 73)

//From Apple's “The Swift Programming Language” guide (https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html)

Потім, привласнюючи квартиру особі, вони використовують знак оклику для "розгортання примірника":

john!.apartment = number73

Що означає "розкрутити екземпляр"? Чому це потрібно? Чим він відрізняється від того, щоб робити наступне:

john.apartment = number73

Я дуже новачок у мові Swift. Просто намагаюся звести основи.


ОНОВЛЕННЯ:
Великий фрагмент головоломки, який мені не вистачало (не вказано безпосередньо у відповідях - принаймні, не під час написання цього запису), це те, що ви робите наступне:

var john: Person?

це НЕ означає, що " johnмає тип Personі він може бути нульовим", як я спочатку думав. Я просто нерозумів це Personі Person?є абсолютно окремими типами. Одного разу я зрозумів, що все інше ?, !безумство та чудові відповіді нижче, мали набагато більше сенсу.

Відповіді:


530

Що означає "розкрутити екземпляр"? Чому це потрібно?

Наскільки я можу попрацювати (це теж для мене дуже нове) ...

Термін "загорнута" означає, що ми повинні думати про необов'язкові змінну як подарунок, загорнутий у блискучий папір, який може (на жаль!) ​​Бути порожнім .

Коли "загорнуте", значення необов'язкової змінної - це перерахунок з двома можливими значеннями (трохи схожим на булеву). Цей перелік описує, чи змінна містить значення ( Some(T)), чи ні ( None).

Якщо є значення, це можна отримати шляхом "розгортання" змінної (отримання Tвід Some(T)).

Чим john!.apartment = number73відрізняється від john.apartment = number73? (Перефразовано)

Якщо ви пишете ім'я необов'язкової змінної (наприклад, текст johnбез значень !), це стосується "загорнутого" перерахунку (Some / None), а не самого значення (T). Отже, johnце не примірник Person, і він не має apartmentчлена:

john.apartment
// 'Person?' does not have a member named 'apartment'

Фактичне Personзначення можна розкрутити різними способами:

  • "вимушене розгортання": john!(дає Personзначення, якщо воно існує, помилка виконання, якщо воно дорівнює нулю)
  • "необов'язкове прив'язування": if let p = john { println(p) }(виконує значення, printlnякщо значення існує)
  • "необов'язковий ланцюжок": john?.learnAboutSwift()(виконує цей створений метод, якщо значення існує)

Я думаю, ви обираєте один із цих способів розгортання, залежно від того, що має статися в нульовій справі, і наскільки це можливо. Ця мовна конструкція змушує чітко оброблятися з випадком нуля, що, напевно, покращує безпеку щодо Obj-C (де легко забути обробити нульовий випадок).

Оновлення :

Знак оклику також використовується в синтаксисі для оголошення "Неявно розгорнуті необов'язкові".

У наведених прикладах johnзмінна була оголошена як var john:Person?та є необов'язковою. Якщо ви хочете фактичне значення цієї змінної, її потрібно розкрутити, використовуючи один із трьох вищевказаних методів.

Якби це було оголошено як var john:Person!замість цього, змінна була б Неявно розгорнутою необов’язковою (див. Розділ із цим заголовком у книзі Apple). Немає необхідності розгортати цю змінну при доступі до значення і johnможе використовуватися без додаткового синтаксису. Але книга Apple говорить:

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

Оновлення 2 :

Стаття Майка Еша " Цікаві функції швидкого переходу" дає певну мотивацію для необов'язкових типів. Я думаю, що це чудово, чітко написано.

Оновлення 3 :

Ще одна корисна стаття про неявно розгорнуте факультативне використання знака оклику: " Швидкий і остання миля " Кріса Адамсона. У статті пояснюється, що це прагматичний захід від Apple, який використовується для декларування типів, що використовуються в їхніх рамках Objective-C, які можуть містити нуль. Оголошення типу як необов'язкового (використання ?) або неявного розгортання (використання !) - це "компроміс між безпекою та зручністю". У прикладах, наведених у статті, Apple вирішила оголосити типи як неявно розгорнуті, зробивши код для виклику більш зручним, але менш безпечним.

Можливо, Apple в майбутньому може прочесати свої рамки, усуваючи невизначеність неявно розгорнутих ("мабуть, ніколи нульових") параметрів і замінивши їх необов'язковими ("безумовно, може бути нульовим зокрема (сподіваємось, задокументовано!] Обставинами") або стандартним не -опціональні ("ніколи не нульові") декларації, засновані на точній поведінці їх об'єктивного-C коду.


Я не впевнений у цьому поясненні. Якщо ви просто запустіть код без! воно все одно повертає фактичне значення. Можливо! для швидкості?
Річард Вашингтон

ДОБРЕ. Тож документи говорять про використання! коли точно знаєш, його можна розгортати. Але ви можете запустити код добре без нього (четвертий варіант для вашого списку - неявне розгортання) ТА без попередньої перевірки. Ви отримуєте назад значення або нуль, якщо нуль. Але якщо ви точно знаєте, що це не нуль, тоді використовуйте! ...... але я все ще не можу зрозуміти, чому ви це зробите?
Річард Вашингтон

2
Привіт @RichardWashington - Я додав оновлення до своєї відповіді, яке, сподіваємось, пояснює щось із цього.
Ешлі

Оновлення 3 - саме те, що я шукаю. Перш ніж прочитати це, я подумав, що Apple бреше, коли вони повернули щось на зразок NSURLCredential! що насправді може бути нульовим.
Золотий палець

Дякую за фантастичну відповідь @Ashley. Прочитавши цю відповідь та швидкий код прикладу Apple, мені справді здається, що в цій компромісі між безпекою та продуктивністю, як правило, найкраще бути на безпечнішому боці. Який помітний виняток, який я виявив, коли Apple використовує примусове розгортання елементів інтерфейсу, що має сенс, оскільки по-перше, елементи інтерфейсу зазвичай є необов'язковими при залученні дошка розкадрів (оскільки їм програмно не присвоєно значення), а по-друге, тому що вони майже ніколи не нуля, якщо їх контролер перегляду, що містить, є нульовим, і в цьому випадку це обговорення, якщо суперечить.
Міхір

127

Ось що, на мою думку, полягає в різниці:

var john: Person?

Значить Джон може бути нульовим

john?.apartment = number73

Компілятор інтерпретує цей рядок як:

if john != nil {
    john.apartment = number73
}

Поки

john!.apartment = number73

Компілятор інтерпретує цей рядок як просто:

john.apartment = number73

Отже, використання! розгорнуть оператор if і змусить його запустити швидше, але якщо Джон буде нульовим, то станеться помилка виконання.

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

Оновлення:

Повертаючись до цієї відповіді через 4 роки, коли я отримав найвищу репутацію в ній у Stackoverflow :) Я трохи неправильно зрозумів значення розгортання в той час. Тепер, через 4 роки, я вважаю, що сенс розгортання тут полягає в розширенні коду з його оригінальної компактної форми. Крім того, це означає усунення розпливчастості навколо цього об'єкта, оскільки ми не впевнені, що за визначенням він є нульовим чи ні. Як і відповідь Ешлі вище, подумайте про це як про подарунок, який нічого не може містити в ньому. Але я все ще думаю, що розгортання - це розкручування коду, а не розпакування на основі пам'яті, як використання enum.


9
На моєму майданчику john.apartment = number73 не збирається, ви повинні вказати john? .Apartment = number73
Чак Пінкерт

2
@ChuckPinkert вірно, 4-й рядок слід відредагувати до Джона? .Apartment = number73, хоча приємна відповідь!
Брюс

john.apartment = number73 видає помилку: значення необов'язкового типу "Person?" не розгорнуто: ти мав на увазі використовувати "!" або "?"
павук

4
Розгортання не має нічого спільного з виконанням wrt. "Перевірка на нуль" все-таки повинна бути виконана під час виконання, єдиною різницею є те, що помилка виконання буде видалена у випадку, якщо в Факультативному немає значення.
madidier

67

TL; DR

Що означає знак оклику в мові Swift?

Знак оклику ефективно говорить: «Я знаю, що цей необов’язковий обов’язково має значення; будь ласка, використовуйте його ». Це відомо як примусове розгортання необов'язкового значення:

Приклад

let possibleString: String? = "An optional string."
print(possibleString!) // requires an exclamation mark to access its value
// prints "An optional string."

let assumedString: String! = "An implicitly unwrapped optional string."
print(assumedString)  // no exclamation mark is needed to access its value
// prints "An implicitly unwrapped optional string."

Джерело: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-XID_399


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

Здається, це вже не так. У Swift 5 repl, якщо я це роблю let f: String! = "hello"і тоді print(f), вихід виходить Optional("hello")замість просто "hello".
числоманіяк

37

Якби Джон був необов'язковим var (оголошено таким чином)

var john: Person?

тоді для Джона не було б значення (в мові ObjC, нульове значення)

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

if let otherPerson = john {
    otherPerson.apartment = number73
}

Інтер'єр цього оцінюватиме лише тоді, коли Джон має значення.


4
Дякую за відповідь, тепер я розумію, що говорить знак оклику, я все ще намагаюся обернути голову, чому ... Ви сказали, що це говорить компілятору "Я знаю, що це має значення, вам не потрібно тестувати на це ". Що він купує, коли компілятор не перевіряє його? Без оклику чи компілятор буде помилятись (у мене все ще немає середовища, де я можу перевірити Swift)? Це! 100% необхідний для всіх необов’язкових варіантів? Якщо так, чому Apple потурбувалась цим, на відміну від того, щоб просто зробити так, щоб його не було ! означає "Я знаю, що це має значення, вам не потрібно тестувати його"?
Троя

"компілятор збирається помилитися" - ні, він все ще працює добре, повертаючи значення, як очікувалося. Я цього не розумію. Це! просто для швидкості, коли ви впевнені, можливо?
Річард Вашингтон

Насправді згодом документи розмовляють про неявно розгорнуті необов'язкові параметри, використовуючи наведений нижче приклад: “let possibleString: String? = "An optional string." println(possibleString!) // requires an exclamation mark to access its value // prints "An optional string.” Але це добре працює з ні! Щось тут здається дивним.
Річард Вашингтон

3
@RichardWashington Ви збентежені з розумних причин! Приклади в документах залишають деякі речі поза межами та вводять в оману, оскільки вони лише використовують println. Мабуть, є принаймні пара випадків, коли розгортання факультативів не потрібно. Один - це при використанні println. Інша - при використанні рядкової інтерполяції. То, може, println та рядкова інтерполяція розгортаються під обкладинками? Можливо, хтось має більше розуміння щодо цього. Дивлячись на визначення заголовка бета-версії Xcode6, мені нічого не виявило про цю магію.
mbeaty

Так, я розумію , що John?і Johnдва різних типи. Один - тип Факультативна особа, а другий - Особа типу. Необов’язково потрібно розкрутити особу, перш ніж ви зможете вийти з неї. Але, як ви кажете, це, здається, відбувається принаймні в цих умовах, без того, щоб насправді нічого робити. Це, здається, робить! надлишкові. Якщо тільки! ЗАВЖДИ необов’язковий, але пропонується зробити, щоб знайти помилки під час збирання. Начебто присвоєння vars / lets певному типу може бути явним, let John: Person = ...але також можна зробити висновок let John = ....
Річард Вашингтон

27

Деякі перспективи великої картини, які можна додати до інших корисних, але більш деталізованих відповідей:

У Swift знак оклику з’являється у кількох контекстах:

  • Примусове розгортання: let name = nameLabel!.text
  • Неявно розгорнуті опції: var logo: UIImageView!
  • Примусове лиття: logo.image = thing as! UIImage
  • Неопрацьовані винятки: try! NSJSONSerialization.JSONObjectWithData(data, [])

Кожен з них - це інша мовна конструкція з різним значенням, але всі вони мають три важливі речі:

1. Знак оклику оминає перевірки безпеки Свіфта за час збирання.

Коли ви користуєтесь !Swift, ви по суті говорите: "Ей, компілятор, я знаю, ти думаєш, що тут може статися помилка , але я знаю з цілковитою впевненістю, що цього ніколи не буде".

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

Однак щоразу, коли ви використовуєте !, ви виключаєте наявність помилки на відновлення, що означає, що ...

2. Знак оклику - це потенційні аварії.

Знак оклику також говорить: "Ей, Свіфт, я настільки впевнена, що ця помилка ніколи не може трапитися, що вам краще зірвати всю програму, ніж мені, щоб кодувати шлях відновлення для неї".

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

Однак, коли я бачу !в дикій природі, його рідко використовують так розумно. Натомість це занадто часто означає: "це значення було необов’язковим, і я насправді не надто важко думав, чому він може бути нульовим або як правильно впоратися з цією ситуацією, але додавання !змусило його скласти ... так що мій код правильний, правда?"

Остерігайтеся зарозумілості оклику. Натомість…

3. Знак оклику найкраще використовувати вкрай сумно.

Кожна з цих !конструкцій має ?аналог, який змушує вас мати справу з випадком помилки / нуля:

  • Умовне розгортання: if let name = nameLabel?.text { ... }
  • Необов’язково: var logo: UIImageView?
  • Умовні ролі: logo.image = thing as? UIImage
  • Винятки з відмовою від відмови: try? NSJSONSerialization.JSONObjectWithData(data, [])

Якщо ви спокушаєтесь користуватися !, завжди добре продумати, чому ви не використовуєте ?натомість. Чи є збій вашої програми справді найкращим варіантом, якщо !операція не працює? Чому це значення необов’язкове / недоступне?

Чи є розумний шлях відновлення, який ваш код може пройти у випадку нуля / помилки? Якщо так, кодуйте це.

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

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

Я періодично шукаю всю свою кодову базу !та перевіряю кожне її використання. Дуже мало звичаїв стоять на огляд. (Станом на це написання, у всій системі Siesta є рівно два екземпляри .)

Це не означає, що ви ніколи не повинні використовувати !у своєму коді - просто, щоб ви користувалися ним усвідомлено , і ніколи не робили його параметром за замовчуванням.


func isSubscriptionActive(receiptData: NSDictionary?) -> Bool { if(receiptData == nil) { return false; } return (hasValidTrial(receiptData!) || isNotExpired(receiptData!)) && isNotCancelled(receiptData!) } Дано 3. Чи є кращий спосіб його написати?
jenson-button-event

Можна сказатиfunc isSubscriptionActive(receiptData: NSDictionary?) -> Bool { guard let nonNilReceiptData = receiptData else { return false} return (hasValidTrial(nonNilReceiptData) || isNotExpired(nonNilReceiptData)) && isNotCancelled(nonNilReceiptData) }
rkgibson2

Знак оклику не обходить жодних перевірок безпеки. Ви отримуєте гарантований збій, якщо необов’язковий нуль. Необов’язково завжди перевіряється. Це просто дуже жорстокий спосіб зробити перевірку безпеки.
gnasher729

@ gnasher729 Як йдеться у відповіді, він уникає перевірок безпеки під час компіляції на користь безпечного збою в процесі виконання .
Пол Кантрелл

24

johnє необов'язковим var. Так може бути містить nilзначення. Щоб переконатися, що значення не є нульовим, використовуйте в !кінці varімені.

З документації

"Після того, як ви впевнені, що необов'язковий містить значення, ви можете отримати доступ до його базового значення, додавши знак оклику (!) До кінця імені необов'язкового. Знак оклику ефективно говорить: «Я знаю, що цей необов’язковий обов’язково має значення; будь ласка, використовуйте його ».

Ще один спосіб перевірити значення нуля - це

    if let j = json {
        // do something with j
    }

1
Так, я бачив це в документації, але це все ще здається непотрібним ... як, на мене, john.apartment = number73також сказано: "Я знаю, що цей необов'язковий обов'язково має значення; будь ласка, використовуйте його" ...
Трой

6
Так, це так, але справа в тому, що за замовчуванням Swift намагається вловлювати помилки під час компіляції. Якщо ви знаєте чи вважаєте, що знаєте, що ця змінна не може містити нуля, ви можете зняти цю перевірку за допомогою знака оклику.
lassej

16

Ось кілька прикладів:

var name:String = "Hello World"
var word:String?

Де wordнеобов'язкове значення. означає, що вона може містити або не може містити значення.

word = name 

Тут nameє значення, щоб ми могли призначити його

var cow:String = nil
var dog:String!

Де dogнасильно розгорнуто, це означає, що воно повинно містити значення

dog = cow

Додаток вийде з ладу, оскільки нас призначають nilрозгорнутим


1
var c:Int = nilотримає: "Nil не може ініціалізувати вказаний тип" int ""
Asususer

15

В цьому випадку...

вар Джон: Особа!

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


1
Дякую. Я також знайшов таке посилання, яке пояснює це. Цей тип називається "неявно розгорнутим варіантом". stackoverflow.com/questions/24122601 / ...
Kaydell

5

Якщо ви родом з мови сімейства C, ви будете думати "вказівник на об'єкт типу X, який може бути адресою пам'яті 0 (NULL)", а якщо ви переходите з динамічно набраної мови, ви будете думаючи: "Об'єкт, який, ймовірно, типу X, але може бути не визначеним". Жодне з них насправді не є правильним, хоча в обхідному напрямку перший є близьким.

Те, як ви повинні думати про це, як ніби це такий предмет, як:

struct Optional<T> {
   var isNil:Boolean
   var realObject:T
}

Коли ви тестуєте необов’язкове значення, foo == nilвоно справді повертається foo.isNil, і коли ви говорите, що foo!воно повертається foo.realObjectз твердженням, що foo.isNil == false. Важливо зазначити це, оскільки якщо fooнасправді це нуль, коли ви це робите foo!, це помилка під час виконання, тому зазвичай ви хочете використовувати замість цього умовне, якщо ви не впевнені, що значення не буде нульовим. Цей вид хитрості означає, що мова може бути сильно набрана, не змушуючи вас перевіряти, чи значення скрізь нульові.

На практиці це по-справжньому не поводиться так, тому що роботу виконує компілятор. На високому рівні існує окремий тип, Foo?який не Fooдозволяє функціям, які приймають тип, Fooотримати нульове значення, але на низькому рівні необов'язкове значення не є справжнім об'єктом, оскільки воно не має властивостей чи методів; ймовірно, що насправді це вказівник, який може за NULL (0) мати відповідний тест при відкручуванні сили.

Існує інша ситуація, коли ви побачите знак оклику на тип, як у:

func foo(bar: String!) {
    print(bar)
}

Це приблизно еквівалентно прийняттю необов'язкового з примусовим зніманням, тобто:

func foo(bar: String?) {
    print(bar!)
}

Ви можете використовувати це, щоб мати метод, який технічно приймає необов'язкове значення, але буде мати помилку виконання, якщо воно дорівнює нулю. У поточній версії Swift це, мабуть, обходить твердження «is-not-nil», тому ви будете мати помилку низького рівня. Як правило, це не дуже гарна ідея, але вона може бути корисною при перетворенні коду з іншої мови.



3

Якщо ви знайомі з C #, це як типи Nullable, які також оголошуються за допомогою знака питання:

Person? thisPerson;

І знак оклику в цьому випадку еквівалентний доступу до властивості .Value такого типу, що змінюється:

thisPerson.Value

2

У об'єктивних змінних C, які не мають значення, було рівним 'nil' (також можна було використовувати значення 'nil' такі ж, як 0 і false), отже, можна було використовувати змінні в умовних операторах (Змінні зі значеннями такі ж, як 'TRUE ", а значення, які не мають значення, дорівнювали" FALSE ").

Swift забезпечує безпеку типу, надаючи "необов'язкове значення". тобто запобігає виникненню помилок при призначенні змінних різних типів.

Так у Swift в умовних заявах можуть бути надані лише булеви.

var hw = "Hello World"

Тут, хоч 'hw' є рядком, він не може бути використаний у операторі if, як у об'єктиві C.

//This is an error

if hw

 {..}

Для цього його потрібно створити як,

var nhw : String? = "Hello World"

//This is correct

if nhw

 {..}

2

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


2

Коротше (!): Після того, як ви оголосили змінну і впевнені, що змінна має значення.

let assumedString: String! = "Some message..."
let implicitString: String = assumedString

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

let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // requires an exclamation mark

1

Джон - необов'язкова особа, це означає, що він може містити значення або бути нульовим.

john.apartment = number73

використовується, якщо john не є обов'язковим. Оскільки Джон ніколи не є нульовим, ми можемо бути впевнені, що він не зателефонує квартирі за нульовим значенням. Поки

john!.apartment = number73

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

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

if convertedNumber {
    println("\(possibleNumber) has an integer value of \(convertedNumber!)")
} else {
    println("\(possibleNumber) could not be converted to an integer")
}

Ви говорите, що якщо Джон є нульовим, а я йду john.apartment = number73, це НЕ БУДЕ до помилки, якщо я не поставлю "!" після Джона?
Троя

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

1
Якщо я повинен використовувати знак оклику для ВСІХ необов'язкових варіантів, то чому тоді Apple навіть не турбується про це, а не робити так, що відсутність знака оклику означає те саме? Це здається непотрібним нальотом ... Або є випадок, коли має сенс НЕ використовувати знак оклику з необов’язковим варом?
Троя

ви використовуєте знак оклику, коли вам потрібно, щоб необов'язково не було нулем. Як і у випадку з доступом до властивостей john. ви могли б зробити щось на кшталт var jack: Person? = Джон, де джек - це також необов'язковий або вак-джек: Особа = Джон! де джек є Особою і не є нульовим
Коннор

Правильно, але, в оригінальному прикладі, чи не факт, що я перенаправляю johnфакультативну, скажіть компілятору, що мені це потрібно, щоб він був нульовим? ... І у вашому var jack: Person = john!прикладі, чи не бракує? після того, як Person скаже компілятору, що вам потрібно johnбути ненульовим? Загалом, !здається, що це здається надлишковим ... Досі відчувається, що я щось пропускаю в частині "чому" !...
Troy

1

Простіше кажучи, знаки оклику означають, що факультатив розгортається. Необов'язково - це змінна, яка може мати значення чи ні - тому ви можете перевірити, чи змінна порожня, використовуючи оператор if let, як показано тут , а потім змусити її розгортати. Якщо ви змусите розкручувати необов’язковий, який порожній, програма перестане працювати, тому будьте обережні! Необов’язкові декларуються, ставлячи знак питання в кінці явного призначення змінної, наприклад, я можу написати:

var optionalExample: String?

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

Сподіваюся, що це допомогло.


1

ПРОСТИМИ СЛОВАМИ

ВИКОРИСТАННЯ Знак оклику вказує, що змінна повинна містити значення нуля (воно ніколи не буде нулем)


1

Весь сюжет починається з функції швидкого під назвою необов'язкові вари. Це вари, які можуть мати значення або не можуть мати значення. Загалом, швидкий не дозволяє нам використовувати змінну, яка не ініціалізується, оскільки це може призвести до збоїв або несподіваних причин, а також подати на місце заповнення заповнювачів. Таким чином, для оголошення змінної, значення якої спочатку не визначається, ми використовуємо "?". Коли така змінна оголошена, щоб використовувати її як частину деякого виразу, слід розгортати їх перед використанням, розгортання - це операція, за допомогою якої виявлено значення змінної, це стосується об'єктів. Без розгортання, якщо ви спробуєте їх використати, ви отримаєте помилку часу компіляції. Для розгортання змінної, яка є необов'язковою вар, знак оклику "!" використовується.

Зараз бувають випадки, коли ви знаєте, що таким необов’язковим змінним буде присвоєно значення системи, наприклад, або вашій власній програмі, але десь пізніше, наприклад, розетки інтерфейсу, в такій ситуації замість оголошення необов'язкової змінної за допомогою питання запитання "?" ми використовуємо "!".

Таким чином система знає, що ця змінна, яка оголошена символом "!" зараз не є обов'язковим і не має значення, але отримає значення пізніше протягом свого життя.

Таким чином, знак оклику містить два різних звичаї: 1. оголосити змінну, яка буде необов'язковою і отримає значення однозначно пізніше;

Наведені вище описи уникають занадто багато технічних речей, я сподіваюся.


1

Якщо ви використовуєте його як необов'язковий, він розгортає необов’язковий і бачить, чи є щось там. Якщо ви використовуєте його в операторі if-else, це код NOT. Наприклад,

if (myNumber != 3){
 // if myNumber is NOT 3 do whatever is inside these brackets.
)

1

Необов’язкова змінна може містити значення, а може бути і ні

випадок 1: var myVar:String? = "Something"

випадок 2: var myVar:String? = nil

тепер якщо ви запитаєте myVar !, ви говорите компілятору повернути значення у випадку 1, воно повернеться "Something"

у випадку 2 вона вийде з ладу.

Значення! mark змусить компілятор повернути значення, навіть якщо його немає. ось чому назва Force Unwrapping .


@Moritz. Я не знаю, це так? Не можу відповісти, якщо на питання вже відповіли? Чи викликаю я якусь проблему чи щось трапилося у моїй відповіді? я не розумію Чому ти заперечуєш, щоб відповісти правильно? Я спробував відповісти, виходячи з свого розуміння концепції. Я думаю, що деякі люди знайдуть це корисним для чіткого та легкого пояснення.
Ashish Pisey

@Moritz Тепер це конструктивно. Ви б мали сказати мені цю корекцію в першу чергу, якби це було причиною. дякую за відгуки я виправив це.
Ашиш Пісей

0
Simple the Optional variable allows nil to be stored.

var str : String? = nil

str = "Data"

To convert Optional to the Specific DataType, We unwrap the variable using the keyword "!"

func get(message : String){
   return
}

get(message : str!)  // Unwapped to pass as String

1
Будь ласка, не пишіть відповіді, які не додають нічого, що раніше не було охоплено попередніми відповідями.
Далія Праснікар

-1

ЗАПИТАЙТЕ СЕБЕ

  • Має типу person?є apartmentчлен / нерухомість? АБО
  • Має типу personє apartmentчлен / нерухомість?

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

Щоб зрозуміти, може знадобитися супер-базовий рівень розуміння Generics . Дивіться тут . Багато речей у Swift написано за допомогою Generics. Необов’язково включено

Код, наведений нижче, став доступним із цього відео на Стенфорді . Дуже рекомендую вам переглянути перші 5 хвилин

Необов’язковий - перерахунок із лише 2 випадками

enum Optional<T>{
    case None
    case Some(T)
}

let x: String? = nil //actually means:

let x = Optional<String>.None

let x :String? = "hello" //actually means:

let x = Optional<String>.Some("hello")

var y = x! // actually means:

switch x {
case .Some(let value): y = value
case .None: // Raise an exception
}

Необов’язкові прив'язки:

let x:String? = something
if let y = x {
    // do something with y
}
//Actually means:

switch x{
case .Some(let y): print)(y) // or whatever else you like using 
case .None: break
}

коли ти кажеш, що var john: Person?ти насправді маєш на увазі таке:

enum Optional<Person>{
case .None
case .Some(Person)
}

Чи має вище перерахування яких - або властивість з ім'ям apartment? Ви його бачите десь? Його взагалі немає! Однак якщо ви розмотаєте його, тобто зробите, person!тоді ви можете ... що він робить під кришкою:Optional<Person>.Some(Person(name: "John Appleseed"))


Якби ви визначили var john: Personзамість: var john: Person?тоді вам більше не потрібно було б !використовувати, тому що Personсам має членapartment


У майбутньому обговорення того, чому використовувати !розмотування, іноді не рекомендується, дивіться це питання


2
Використання фактичних слайдів, наведених вище, можливо, порушення авторських прав: "... Університет Стенфорда зберігає авторські права на весь вміст у нашій колекції iTunes U." , з http://itunes.stanford.edu . Напевно, краще, власними словами, відповісти на вміст, який ви дізналися з курсу, який ви сприймаєте, щоб відповісти на це запитання (і замість використання слайдів, скоріше, цитуйте відповідні частини їх у кодовій формі із посиланнями, природно).
dfri

2
Ваш аргумент - argumentum ad populum . Так чи інакше, я не вірю, що Стенфорд приїде сюди і застосує права DMCA , але завжди краще, якщо ваші відповіді будуть вашими власними словами на основі офіційних / дійсних джерел , а не у формі копіювання цих джерел прямо, як зазначено вище; особливо коли ми говоримо про захищені авторськими правами слайди: знову ж таки, краще процитувати вміст слайдів у кодових блоках (зображення коду, як правило, немає-тут у SO!).
dfri

1
Метапост, до якого я посилався, просто описує підхід SO: s до цього: така посада, як правило, не буде видалена, якщо якийсь випадковий користувач повідомить про це як порушення авторських прав: тільки якщо, наприклад, представник Стенфорда. повинні були прийти сюди, оскільки посаду, яку потрібно зняти, потрібно було б виконувати. Звідси я написав " Я не вірю, що Стенфорд приїде сюди і застосує права DMCA, ..." . Моя думка вище, що я вважаю це можливим порушенням авторських прав (що, на мою думку, є неправильним), але, природно, ніхто ніколи цього не виконуватиме. ...
dfri

1
Але це осторонь, розміщення запитань або відповідей, які містять зображення коду, відверто від багатьох причин. Щоб завершити це: я намагався вказати цими коментарями, що вважаю, що ця відповідь у її нинішньому вигляді не є настільки гарною, як це могло б бути, через дві основні причини (захищені авторськими правами слайди, зображення коду). Звичайно, ніхто, включаючи мене, нічого з цим не зробить, я просто вказую на це для подальшої довідки (або, можливо, рекомендую замість цього редагувати цю публікацію цитатами за кодовими блоками). Погоджено, iTunes U! :)
dfri

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