Что означает восклицательный знак на языке Swift?

Руководство по языку Swift Programming Language имеет следующий пример:

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 "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. Просто пытаюсь убрать основы.


UPDATE:
Большая часть загадки, которую я отсутствовала (не прямо сказано в ответах - по крайней мере, не на момент написания этого) заключается в том, что когда вы делаете следующее:

var john: Person?

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

Ответ 1

Что значит "развернуть экземпляр"? Почему это необходимо?

Насколько я могу это сделать (это для меня тоже очень ново)...

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

Когда "завернутый", значение необязательной переменной представляет собой перечисление с двумя возможными значениями (немного как логическое). Это перечисление описывает, сохраняет ли переменная значение (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() (выполняет этот обработанный метод, если это значение существует)

Я думаю, вы выбираете один из этих способов разворота, в зависимости от того, что должно произойти в случае с nil, и насколько это возможно. Этот языковой дизайн заставляет nil case обрабатываться явно, что, я полагаю, повышает безопасность над Obj-C (там, где легко забыть обработать случай с nil).

Обновление

Восклицательный знак также используется в синтаксисе для объявления "Неявно отключенных опций".

В приведенных примерах переменная john была объявлена ​​как var john:Person?, и она является необязательной. Если вы хотите фактическое значение этой переменной, вы должны развернуть его, используя один из трех методов выше.

Если бы он был объявлен как var john:Person! вместо этого, переменная была бы неявно отключенной опционально (см. раздел с этим заголовком в книге Apple). Нет необходимости разворачивать эту переменную при доступе к значению, а john можно использовать без дополнительного синтаксиса. Но в книге Apple говорится:

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

Обновление 2:

Статья "Интересные быстрые функции" Майка Эша дает некоторую мотивацию для факультативных типов. Я думаю, что это замечательно, ясное письмо.

Обновление 3:

Еще одна полезная статья о неявно развернутом дополнительном использовании для восклицательного знака: "Swift and the Last Mile" Криса Адамсона. В статье объясняется, что это прагматичная мера, которую Apple использовала для объявления типов, используемых их фреймами Objective-C, которые могут содержать нуль. Объявление типа как необязательного (с использованием ?) или неявно развернутого (с использованием !) является "компромиссом между безопасностью и удобством". В примерах, приведенных в этой статье, Apple решила объявить типы как неявно развернутые, что делает код вызова более удобным, но менее безопасным.

Возможно, Apple может расчесывать свои фреймворки в будущем, устраняя неопределенность неявно разворачиваемых параметров ( "возможно, никогда не нуль" ) и заменяя их необязательным ( "наверняка, может быть, ноль, в частности [надеюсь, документированные!] обстоятельства" ) или стандартные необязательные ( "никогда не ноль" ) объявления, основанные на точном поведении их кода Objective-C.

Ответ 2

Вот что я считаю разницей:

var john: Person?

Средство john может быть nil

john?.apartment = number73

Компилятор интерпретирует эту строку как:

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

В то время как

john!.apartment = number73

Компилятор интерпретирует эту строку как просто:

john.apartment = number73

Следовательно, используя! будет разворачивать оператор if и заставить его работать быстрее, но если john равно нулю, тогда произойдет ошибка времени выполнения.

Таким образом, обертка здесь не означает, что она обернута памятью, но это означает, что она обернута кодом, в этом случае она завернута с оператором if, и поскольку Apple уделяет пристальное внимание производительности во время выполнения, они хотят дать вам способ заставьте приложение работать с максимальной производительностью.

Обновить:

Возвращаясь к этому ответу через 4 года, я получил наивысшую репутацию от него в Stackoverflow :) Я неправильно понял значение разворачивания в то время. Теперь, спустя 4 года, я считаю, что смысл разворачивания здесь заключается в расширении кода из его первоначальной компактной формы. Также это означает удаление неопределенности вокруг этого объекта, так как мы не уверены, по определению, это ничто или нет. Как и ответ Эшли выше, подумайте об этом как о подарке, который не может содержать ничего в нем. Но я все еще думаю, что разворачивание - это разворачивание кода, а не разворачивание на основе памяти с использованием перечисления.

Ответ 3

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

Ответ 4

Если john были необязательным var (объявленным таким образом)

var john: Person?

тогда для john было бы невозможно иметь значение (в языке ObjC, значение nil)

Восклицательный знак в основном говорит компилятору "Я знаю, что это имеет значение, вам не нужно его проверять". Если вы не хотите его использовать, вы можете условно протестировать его:

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

Внутри этого будет только оценка, имеет ли значение john.

Ответ 5

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

В Swift восклицательный знак появляется в нескольких контекстах:

  • Принудительная развертка: let name = nameLabel!.text
  • Неявно развернутые var logo: UIImageView!: var logo: UIImageView!
  • Принудительное литье: logo.image = thing as! UIImage logo.image = thing as! UIImage
  • Необработанные исключения: try! NSJSONSerialization.JSONObjectWithData(data, []) try! NSJSONSerialization.JSONObjectWithData(data, [])

Каждая из них - это другая языковая конструкция с другим смыслом, но все они имеют три важные вещи:

1. Восклицательные знаки обходят Sweeps во время проверки безопасности.

Когда вы используете ! в Swift вы, по сути, говорите: "Эй, компилятор, я знаю, что вы думаете, что здесь может произойти ошибка, но я знаю с полной уверенностью, что этого никогда не будет".

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

Однако, когда вы используете ! , вы исключаете путь восстановления для ошибки, а это значит, что...

2. Восклицательными знаками являются потенциальные сбои.

Восклицательный знак также гласит: "Эй, Свифт, я настолько уверен, что эта ошибка никогда не может произойти, что лучше для вас разрушить все мое приложение, чем для меня, чтобы закодировать путь восстановления для него".

Это опасное утверждение. Это может быть правильно: в критическом критически важном коде, где вы много думали о своих инвариантах кодов, может быть, что фиктивный вывод хуже, чем сбой.

Однако, когда я вижу ! в дикой природе, он редко используется так мысленно. Вместо этого это слишком часто означает, что "это значение было необязательным, и я не слишком много думал о том, почему он может быть нулевым или как правильно обрабатывать эту ситуацию, но добавление ! Сделало его компиляцией... так что мой код правильный, не так ли?"

Остерегайтесь высокомерия восклицательного знака. Вместо…

3. Восклицательные знаки лучше всего использовать экономно.

Каждый из них ! Конструкции имеют ? который заставляет вас иметь дело с ошибкой/ноль:

  • Условное развертывание: if let name = nameLabel?.text {... }
  • var logo: UIImageView?: var logo: UIImageView?
  • Условные отливки: logo.image = thing as? UIImage logo.image = thing as? UIImage
  • Исключения Nil-on-failure: try? NSJSONSerialization.JSONObjectWithData(data, []) try? NSJSONSerialization.JSONObjectWithData(data, [])

Если вас искушают использовать ! , всегда хорошо подумать, почему вы не используете ? вместо. Сбой вашей программы действительно лучший вариант, если ! не работает? Почему это значение опционально/невозможно?

Есть ли разумный путь восстановления, который ваш код мог бы принять в случае nil/error? Если да, то код.

Если он не может быть nil, если ошибка не может произойти, то есть ли разумный способ переделать вашу логику, чтобы компилятор это знал? Если да, сделайте это; ваш код будет менее подвержен ошибкам.

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

Я периодически просматриваю всю свою кодовую базу ! и проверять каждое его использование. Очень немногие обычаи стоят на месте. (На момент написания этой статьи вся инфраструктура Siesta имеет ровно два экземпляра.)

Это не значит, что вы никогда не должны использовать ! в вашем коде - просто вы должны использовать его сознательно и никогда не делаете его по умолчанию.

Ответ 6

john является необязательным var. Таким образом, может быть указано значение nil. Чтобы гарантировать, что значение не равно nil, используйте ! в конце имени var.

Из документации

"После того, как вы убедитесь, что значение параметра необязательно содержит значение, вы можете получить доступ к его базовому значению, добавив восклицательный знак (!) до конца имени опций. Восклицательный знак эффективно говорит:" Я знаю, что эта опция, безусловно, имеет значение; используйте его.

Другой способ проверить значение nil равно

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

Ответ 7

Вот несколько примеров:

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

Где word - необязательное значение. означает, что оно может содержать или не содержать значение.

word = name 

Здесь name имеет значение, поэтому мы можем назначить его

var cow:String = nil
var dog:String!

Где dog принудительно разворачивается означает, что он должен содержать значение

dog = cow

Приложение выйдет из строя, потому что мы назначаем nil для распаковки

Ответ 8

В этом случае...

var John: Person!

это означает, что вначале у Джона будет значение nil, оно будет установлено, и один раз установить никогда больше не будет nil-led. Поэтому для удобства я могу использовать более простой синтаксис для доступа к необязательному var, потому что это "неявно развернутый необязательный"

Ответ 9

var currentTitle:String?
//currentTitle = "Title Name"
println("title: \(currentTitle?)") //title: nil
println("title: \(currentTitle!)") //runtime exception

var currentTitle:String?
currentTitle = "Title Name"
println("title: \(currentTitle?)") //title: Optonal("Title Name")
println("title: \(currentTitle!)") //title: Title Name

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

Ответ 10

Если вы пришли с языка C-семейства, вы будете думать "указатель на объект типа X, который может быть адресом памяти 0 (NULL)", и если вы исходите из динамически типизированного языка, вы "Будет думать" Объект, который, вероятно, имеет тип X, но может иметь тип undefined ". Ни один из них не является действительно правильным, хотя по обходному пути первый из них близок.

То, как вы должны думать об этом, как будто это объект вроде:

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

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

На практике это действительно не так, потому что работа выполняется компилятором. На высоком уровне существует тип Foo?, который разделен на foo, и это предотвращает funcs, которые принимают тип foo от получения значения nil, но на низком уровне необязательное значение не является истинным объектом, потому что он не имеет свойств или методов; вероятно, что на самом деле это указатель, который может выполняться с помощью NULL (0) с соответствующим тестом при развертывании силы.

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

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

Это примерно эквивалентно принятию необязательного с принудительной разверткой, то есть:

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

Вы можете использовать это, чтобы иметь метод, который технически принимает необязательное значение, но будет иметь ошибку времени выполнения, если он равен нулю. В текущей версии Swift это, по-видимому, обходит утверждение is-not-nil, поэтому вместо этого вы получите ошибку низкого уровня. Как правило, это не очень хорошая идея, но может быть полезно при преобразовании кода с другого языка.

Ответ 12

Если вы знакомы с С#, это похоже на типы Nullable, которые также объявляются с помощью вопросительного знака:

Person? thisPerson;

И восклицательный знак в этом случае эквивалентен доступу к свойству .Value типа nullable, например:

thisPerson.Value

Ответ 13

В объектных переменных 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

 {..}

Ответ 14

The! в конце объекта говорится, что объект является необязательным и разворачивается, если он в противном случае возвращает нуль. Это часто используется для улавливания ошибок, которые в противном случае могли бы вывести из строя программу.

Ответ 15

В коротком (!): После того как вы объявите переменную и убедитесь, что переменная имеет значение.

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

иначе вам придется делать это на каждом после прохождения значения...

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

Ответ 16

Джон является необязательным Лицом, то есть он может содержать значение или быть ноль.

john.apartment = number73

используется, если john не является необязательным. Поскольку john никогда не является ничто, мы можем быть уверены, что он не будет звонить в квартиру по номиналу. В то время как

john!.apartment = number73

promises компилятор, который john не равен nil, затем разворачивает необязательный, чтобы получить значение john и получает доступ к собственности квартиры john. Используйте это, если вы знаете, что Джон не ноль. Если вы вызовете это по необязательному полю, вы получите ошибку времени выполнения.

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

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

Ответ 17

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

var optionalExample: String?

Эта переменная не имеет значения. Если бы я развернул его, программа выскочила бы, и Xcode сообщит вам, что вы попытались развернуть необязательный параметр со значением nil.

Надеюсь, что это помогло.

Ответ 18

В ПРОСТОЙ СЛОВАХ

ИСПОЛЬЗОВАНИЕ Восклицательный знак указывает, что переменная должна состоять из значения, отличного от нуля (оно никогда не будет равно нулю)

Ответ 19

Вся история начинается с черты быстрых, называемых необязательными vars. Это вары, которые могут иметь значение или могут не иметь значения. В общем, swift не позволяет нам использовать переменную, которая не инициализирована, так как это может привести к сбоям или непредвиденным причинам, а также серверу заполнителя для бэкдоров. Таким образом, чтобы объявить переменную, значение которой изначально не определено, мы используем '?'. Когда такая переменная объявлена, чтобы использовать ее как часть некоторого выражения, необходимо развернуть их перед использованием, разворачивание - это операция, через которую обнаружено значение переменной, это относится к объектам. Без разворачивания, если вы попытаетесь использовать их, у вас будет ошибка времени компиляции. Чтобы развернуть переменную, которая является необязательным var, восклицательный знак "!" используется.

Теперь есть моменты, когда вы знаете, что таким дополнительным переменным будут присваиваться значения, например, системой или вашей собственной программой, но когда-нибудь позже, например, выходы UI, в такой ситуации вместо объявления необязательной переменной с помощью вопросительного знака "?" мы используем "!" .

Таким образом, система знает, что эта переменная, объявленная с помощью "!" является необязательным прямо сейчас и не имеет значения, но получит значение позже в течение его жизни.

Таким образом, восклицательный знак имеет два разных режима, 1. Объявить переменную, которая будет необязательной и получит значение определенно позже 2. Чтобы развернуть необязательную переменную перед ее использованием в выражении.

Над описаниями избегает слишком много технических деталей, я надеюсь.

Ответ 20

Если вы используете его как необязательный, он разворачивает опцию и видит, есть ли что-то. Если вы используете его в инструкции if-else, это код для NOT. Например,

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

Ответ 21

Необязательная переменная может содержать значение или может не быть

случай 1: var myVar:String? = "Something"

случай 2: var myVar:String? = nil

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

в случае 2 произойдет сбой.

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

Ответ 22

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

Ответ 23

СПРОСИТЕ СЕБЯ

  • Имеет ли тип person? член/свойство apartment? ИЛИ
  • Имеет ли тип person член/свойство apartment?

Если вы не можете ответить на этот вопрос, продолжайте чтение:

Чтобы понять, что вам может потребоваться супер-базовый уровень понимания Generics. См. здесь. Многие вещи в Swift написаны с использованием Generics. Включенные опции

Ниже приведен код из этого видео в Стэнфорде. Очень рекомендую вам смотреть первые 5 минут

Необязательный - это перечисление с двумя случаями

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


В качестве будущей дискуссии о том, почему использование ! для разворачивания иногда не рекомендуется, см. этот Q & A