Способы извлечения опционала swift

Опционалы в языке программирования Swift

Константы и переменные любого типа данных в языке программирования Swift (строкового, целочисленного, числового с плавающей точкой, логического и так далее) всегда имеют некое непустое значение. Это относится также к переменным, хранящим ссылки на объекты классов: даже в этом случае переменная не может содержать никакой ссылки, она всегда указывает на какой-либо объект. (Это очень принципиальное отличие, например, от языка программирования C#, где все ссылочные переменные могут быть пустыми, то есть равными nil.)

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

Опционал (другими словами, опциональный тип данных) предоставляет собой определение переменной, реализующее следующие две возможности: переменная-опционал может содержать значение некоего определенного типа данных, называемого в дальнейшем базовым, либо не содержать ничего вообще. Для того чтобы показать отсутствие значение у переменной-опционала, нужно присвоить ей особое значение nil.

Итак, в общем виде определяется опционал следующим образом:
Optional (полный вариант записи опционала) или Type! (краткий вариант записи), где Type — базовый тип.

Например:
var x: Optional
var y: Int?

Оба варианта записи означают, что переменные x и y могут содержать целое число или не содержать ничего (быть равными nil). Также следует отметить, что оба варианта записи создадут переменные одинакового типа (то есть в дальнейшем запись x = y не вызовет ошибки несоответствия типов данных).
(Проводя дальнейшее сравнение с программированием в C#, опционал в Swift — это, по сути, обычный ссылочный тип данных в C#. Например, String? языка Swift аналогичен обычному String в C#).

Важно: при программировании на Swift при объявления переменной-опционала необходимо явно указать ее опциональный тип:

var s: String? = «строка-опционал»

(В случае записи var s = «строка» будет создана переменная обычного типа String. В случае записи var s = nil компилятором будет выдана ошибка «‘nil’ requires a contextual type» — nil требует контекстного типа).

При работе с опционалами нельзя выполнять операции непосредственно между переменными опционального и базового типа. Это приведет к ошибке во время компиляции. Пример:

var x1: Int = 111
var x2: Int? = 222

Выполнение выражения x1 + x2 вызовет ошибку «Value of optional type Int? must be unwrapped to value Int» (значение опционала должно быть распаковано). Иными словами, значение переменной-опционала должно быть неким образом преобразовано к базовому типу.

При программировании на Swift существует несколько таких механизмов преобразования опционала к базовому типу:
• force unwrapping;
• implicity unwrapping;
• optional bunding;
• nil coalescing.

Механизм force unwrapping

Механизм force unwrapping (принудительная распаковка) явно преобразует опционал в значение базового типа путем добавления восклицательного знака после имени переменной-опционала.

Например:
var x1: Int = 10
var x2: Int? = 20
var x3 = x1 + x2!

При таком способе получения значения опционала, содержащее nil, возникает ошибка во время выполнения программы.

Выполнение выражения i! приведет к ошибке при выполнении программы «Unexpectedly found nil while unwrapping an optional value» (Неожиданное обнаружение nil во время распаковки значения опционала).

При использовании механизма force unwrapping для получения значения переменной-опционала необходимо быть уверенным, что опционал имеет непустое значение. Для выполнения такой проверки используется условный оператор и операторы сравнения опционала с nil (равенства «==» и неравенства «!=» ).

Например:
let x1: Int = 333
let x2: Int? = 444
if x2 != nil

Механизм implicity unwrapping

Другим вариантом извлечения значения переменной-опционала является механизм implicity unwrapping (неявного извлечения).

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

Именно в таких случаях используется механизм implicity unwrapping . Для этого при инициализации опционала знак вопроса (?) заменяется на знак восклицания (!).

let str1: String! = «implicity unwrapping»
let str2: String? = «optional string»
let str3: String! = str2! // другой вариант неявной распаковки опционала

Механизм optional binding

Механизм optinal binding (связывание опционала) используется для определения наличия в опционала непустого значения и, в случае если это так, получения значение опционала во временную переменную или константу.

В общем виде механизм optional binding выглядит следующим образом:
if let временная переменная = опционал < //действиядля непустого опционала >
else < // действия для пустого опционала>

В случае, если опционал содержит непустое значение, создается временная переменная (или константа), в которую будет записано распакованное значение опционала, и выполнены действия тела условного оператора (значение опционала доступно через созданную временную переменную (константу), имеющую базовый тип; также необходимо отметить, что, учитывая правила определения области видимости переменных и констант в Swift, временная переменная будет доступна только в блоке действия для непустого опционала ).

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

var x1: Int = 100
var x2: Int? = 200
if let x_temp = x2

Механизм nil coalescing

Еще одним вариантом распаковки опционала является механизм nil coalescing (соединения с nil) с использованием оператора ?? .
В общим виде такой способ выглядит следующим образом:
let переменная = опционал ?? значениепоумолчанию

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

В примере приведенный механизм реализуется следующим образом:
let x1: Int = 1
let x2: Int? = 2
let temp = x2 ?? 0
let x3 = x1 + temp

Наиболее безопасными и поэтому рекомендуемыми механизмами работы с опционалами являются optional binding и nil coalescing .

Если вам интересно развитие в сфере разработки iOS-приложений, предлагаем ознакомиться с курсами iOS-разработки: базовый уровень, продвинутый уровень.

Источник

Опционалы в Swift

Несмотря на некоторый опыт в мобильной разработке (в том числе с применением Swift), регулярно на почве свифтовых опционалов возникали ситуации, когда я знал что нужно делать, но не совсем внятно представлял, почему именно так. Приходилось отвлекаться и углубляться в документацию — количество «заметок на полях» пополнялось с удручающей периодичностью. В определенный момент они достигли критической массы, и я решил упорядочить их в едином исчерпывающем руководстве. Материал получился довольно объемным, поскольку предпринята попытка раскрыть тему максимально подробно. Статья будет полезна как начинающим Swift-разработчикам, так и матерым профессионалам из мира Objective-C — есть ненулевая вероятность, что и последние найдут для себя что-то новое. А если не найдут, то добавят свое новое в комментарии, и всем будет польза.

Читайте также:  Способ определения по следам выстрела места нахождения стрелявшего

Что такое Optionals?

Optionals (опционалы) — это удобный механизм обработки ситуаций, когда значение переменной может отсутствовать. Значение будет использовано, только если оно есть.

Зачем нужны Optionals, когда есть проверка на nil?

Во-первых, проверка на равенство/неравенство nil применима только к nullable-типам и не применима к примитивным типам, структурам и перечислениям. Для обозначения отсутсвия значения у переменной примитивного типа приходится вводить спецзначения, такие как NSNotFound.

NSNotFound не только нужно рассматривать как спецзначение, но и следить, чтобы оно не входило в множество допустимых значений переменной. Ситуация усложняется еще и тем, что NSNotFound считается равным NSIntegerMax, т.е. может иметь разные значения для разных (32-bit/64-bit) платформ. Это значит, что NSNotFound нельзя напрямую записывать в файлы и архивы или использовать в Distributed Objects.

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

Во-вторых: явная опциональность проверяется на этапе компиляции, что снижает количество ошибок в runtime. Опциональную переменную в Swift нельзя использовать точно так же, как неопциональную (за исключением неявно извлекамых опционалов, подробности в разделе Implicit Unwrapping). Опционал нужно либо принудительно преобразовывать к обычному значению, либо использовать специальные преобразующие идиомы, такие как if let , guard let и ?? . Опционалы в Swift реализуют не просто проверку, но целую парадигму опционального типа в теории типов.

В-третьих, опционалы синтаксически более лаконичны, чем проверки на nil , что особенно хорошо видно на цепочках опциональных вызовов — так называемый Optional Chaining.

Как это работает?

Опционал в Swift представляет из себя особый объект-контейнер, который может содержать в себе либо nil , либо объект конкретного типа, который указывается при объявлении этого контейнера. Эти два состояния обозначаются терминами None и Some соответственно. Если при создании опциональной переменной присваемое значение не указывать, то nil присваивается по умолчанию.

В документации значение по умолчанию в случае отсутсвия явного присвоения не упоминается, но сказано, что опционал represents either a wrapped value or nil, the absence of a value. Если опциональная переменная объявлена без явного присвоения (какое-либо Some не присваивалось), то логично следует, что неявно присваивается None — третьего «неинициализрованного» состояния у опционалов нет.

Опционал объявляется посредством комбинации имени типа и лексемы ? . Таким образом, запись Int? — это объявление контейнера, экземпляр которого может содержать внутри nil (состояние None Int) либо значение типа Int (состояние Some Int). Именно поэтому при преобразовании Int? в Int используетя термин unwrapping вместо cast, т.е. подчеркивается «контейнерная» суть опционала. Лексема nil в Swift обозначает состояние None, которое можно присвоить любому опционалу. Это логично приводит к невозможности присвоить nil (состояние None) переменной, которая не является опционалом.

По факту опционал представляет собой системное перечисление:

Перечисление Optional имеет два возможных состояния: .none и some(Wrapped) . Запись Wrapped? обрабатывается препроцессором (Swift’s type system) и трансформируется в Optional , т.е. следующие записи эквивалентны:

Лексема nil по факту обозначает Optional.none , т.е. следующие записи эквивалентны:

Перечисление Optional имеет два конструктора. Первый конструктор init(_ some: Wrapped) принимает на вход значение соответсвующего типа, т.е. следующие записи эквивалентны:

Второй конструктор init(nilLiteral: ()) является реализацией протокола ExpressibleByNilLiteral

и инициализирует опциональную переменную состоянием .none . Этот конструктор используется компилятором. Согласно документации его не рекомендуется вызывать напрямую

что логично, поскольку преобразование пустого кортежа Void () в nil несколько неочевидно.

Вместо этого конструктора следует использовать

или вообще не использовать явное присвоение

поскольку nil будет присвоен по умолчанию.

Перечисление Optional также содержит свойство unsafelyUnwrapped, которое предоставляет доступ на чтение к .some -значению опционала:

Если опционал находится в состоянии .none , обращение к unsafelyUnwrapped приведет к серьезному сбою программы.

В режиме отладки debug build -Onone будет ошибка рантайма:

В релизной сборке optimized build -O будет ошибка рантайма либо неопределенное поведение. Более безопасной операцией является Force Unwrapping (или Explicit Unwrapping) — принудительное извлечение .some -значения, обозначаемое лексемой ! . Применение Force Unwrapping к опционалу в состоянии .none приведет к ошибке рантайма:

Идиомы использования

Нет особого смысла использовать обычное перечисление с двумя состояниями. Вполне можно реализовать подобный механизм самостоятельно: создать enum c двумя состояниями и конструкторами для соответствующих значений, добавить какой-нибудь постфиксный оператор для Force Unwrapping (например, как это сделано здесь), добавить возможность сравнения с nil или вообще придумать «свой» nil и т.д. Опционалы должны быть интегрированы непосредственно в сам язык, чтобы их использование было естественным, не чужеродным. Разумеется, можно рассматривать такую интеграцию как «синтаксический сахар», однако языки высокого уровня для того и существуют, чтобы писать (и читать) код на них было легко и приятно. Использование опционалов в Swift подразумевает ряд идиом или особых языковых конструкций, которые помогают уменьшить количество ошибок и сделать код более лаконичным. К таким идиомам относятся Implicit Unwrapping, Optional Chaining, Nil-Coalescing и Optional Binding.

Implicit unwrapping

Безопасное использование Force Unwrapping подразумевает предварительную проверку на nil , например, в условии if :

Иногда из структуры программы очевидным образом следует, что переменная технически является опционалом, но к моменту первого использования всегда находится в состоянии .some , т.е. не является nil . Для использования опционала в неопциональном контексте (например, передать его в функцию с параметром неопционального типа) приходится постоянно применять Force Unwrapping с предварительной проверкой, что скучно и утомительно. В этих случаях можно применить неявно извлекаемый опционал — Implicitly Unwrapped Optional. Неявно извлекамый опционал объявляется посредством комбинации имени типа и лексемы ! :

В вызове sayHello(times: my_variable2) извлечение значения 42 из my_variable2 все равно осуществляется, только неявно. Использование неявно извлекаемых опционалов делает код более удобным для чтения — нет восклицательных знаков, которые отвлекают внимание (вероятно, читающего код будет беспокоить использование Force Unwrapping без предварительной проверки). На практике это скорее анти-паттерн, увеличивающий вероятность ошибки. Неявно извлекаемый опционал заставляет компилятор «закрыть глаза» на то, что опционал используется в неопциональном контексте. Ошибка, которая может быть выявлена во время компиляции (вызов sayHello(times: my_variable1) ), проявится только в рантайме (вызов sayHello(times: my_variable3) ). Явный код всегда лучше неявного. Логично предположить, что такое снижение безопасности кода требуется не только ради устранения восклицательных знаков, и это действительно так.

Неявно извлекаемые опционалы позволяют использовать self в конструкторе для иницализации свойств и при этом:

  • не нарушать правил двухэтапной инициализации (в конструкторе все свойства должны быть инициализированы до обращения к self ) — иначе код просто не скомпилируется;
  • избежать излишней опциональности в свойстве, для которого она не требуется (по своему смыслу значение свойства не может отсутствовать).
Читайте также:  Народный способ лечения боли груди

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

В этом примере экземпляры классов Country и City должны иметь ссылки друга на друга к моменту завершения инициализации. У каждой страны обязательно должна быть столица и у каждой столицы обязательно должна быть страна. Эти связи не являются опциональными — они безусловны. В процессе инициализации объекта country необходимо инициализировать свойство capitalCity . Для инициализации capitalCity нужно создать экземпляр класса City. Конструктор City в качестве параметра требует соответствующий экземпляр Country, т.е. требует доступ к self . Сложность в том, что экземпляр Country на этот момент еще не до конца инициализирован, т.е. self использовать нельзя.

Эта задача имеет изящное решение: capitalCity объявляется мутабельным неявно извлекаемым опционалом. Как и любой мутабельный опционал, capitalCity по умолчанию инициализируется состоянием nil , т. е. к моменту вызова конструктора City все свойства объекта country уже инициализированы. Требования двухэтапной инициализации соблюдены, конструктор Country находится во второй фазе — можно передавать self в конструктор City. capitalCity является неявным опционалом, т.е. к нему можно обращаться в неопциональном контексте без добавления ! .

Побочным эффектом использования неявно извлекаемого опционала является «встроенный» assert : если capitalCity по каким-либо причинам останется в состоянии nil , это приведет к ошибке рантайма и аварийному завершению работы программы.

Другим примером оправданного использования неявно извлекаемых опционалов могут послужить инструкции @IBOutlet : контекст их использования подразумевает, что переменной к моменту первого обращения автоматически будет присвоено .some -значение. Если это не так, то произойдет ошибка рантайма. Автоматическая генерация кода в Interface Builder создает свойства с @IBOutlet именно в виде неявных опционалов. Если такое поведение неприемлемо, свойство с @IBOutlet можно объявить в виде явного опционала и всегда обрабатывать .none -значения явным образом. Как правило, все же лучше сразу получить «падение», чем заниматься долгой отладкой в случае случайно отвязанного @IBOutlet -свойства.

Optional Chaining

Optional Chaining — это процесс последовательных вызовов по цепочке, где каждое из звеньев возвращает опционал. Процесс прерывается на первом опционале, находящемся в состоянии nil — в этом случае результатом всей цепочки вызовов также будет nil . Если все звенья цепочки находятся в состоянии .some , то результирующим значением будет опционал с результатом последнего вызова. Для формирования звеньев цепочки используется лексема ? , которая помещается сразу за вызовом, возвращающим опционал. Звеньями цепочки могут любые операции, которые возвращают опционал: обращение к локальной переменной (в качестве первого звена), вызовы свойств и методов, доступ по индексу.

Optional сhaining всегда работает последовательно слева направо. Каждому следующему звену передается .some -значение предыдущего звена, при этом результирующее значение цепочки всегда является опционалом, т.е. цепочка работает по следующим правилам:

  • первое звено должно быть опционалом;
  • после лексемы ? должно быть следующее звено;
  • если звено в состояни .none , то цепочка прерывает процесс вызовов и возвращает nil ;
  • если звено в состояни .some , то цепочка отдает .some -значение звена на вход следующему звену (если оно есть);
  • если результат последнего звена является опционалом, то цепочка возвращает этот опционал;
  • если результат последнего звена не является опционалом, то цепочка возвращает этот результат, «завернутый» в опционал (результат вычислений присваивается .some -значению возвращаемого опционала).

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

Optional сhaining не увеличивает уровень вложенности возвращаемого опционала. Тем не менее, это не исключает ситуации, когда результирующим значением какого-либо звена является опционал с несколькими уровнями вложенности. В таких ситуациях для продолжения цепочки необходимо прописать ? в количестве, равном количеству уровней вложенности:

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

Цепочка UIApplication.shared.delegate?.window. frame фактически состоит из четырех звеньев: UIApplication.shared.delegate? , .frame и два звена, объединенных в одном вызове .window?? . Второе «двойное» звено представлено опционалом второго уровня вложенности.

Важной особенностью этого примера также является особый способ формирования двойного опционала, отличающийся от способа формирования doubleOptionalValue в предыдущем примере. UIApplication.shared.delegate!.window является опциональным свойством, в котором возвращается опционал. Опциональность свойства означает, что может отсутствовать само свойство, а не только .some -значение у опционала, возвращаемого из свойства. Опциональное свойство, также как и все прочие свойства, может возвращать любой тип, не только опциональный. Опциональность такого рода формируется в @objc-протоколах с помощью модификатора optional :

В протоколах с опциональными свойствами и методами (иначе, опциональными требованиями) модификатор @objc указывается для каждого опционального требования и для самого протокола. На протокол UIApplicationDelegate из примера выше это требование не распространяется, т.к. он транслируется в Swift из системной библиотеки на Objective-C. Вызов нереализованного опционального требования у объекта, принимающего такой протокол, возвращает опционал соответствующего типа в состоянии .none . Вызов реализованного опционального требования возвращает опционал соответсвующего типа в состоянии .some . Таким образом, опциональные свойства и методы, в отличие от optional сhaining, увеличивают уровень вложенности возвращаемого опционала. Опциональный метод, как и свойство, «заворачивается» в опционал полностью — в .some -значение помещается метод целиком, а не только возращаемое значение:

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

  • могут быть реализованы только в классах, унаследованных от классов Objective-C, либо других классов с аттрибутом @objc (т.е. не могут быть реализованы в структурах и перечислениях);
  • модификатор optional неприменим к требованиям-конструкторам init ;
  • cвойства и методы с аттрибутом @objc имеют ограничение на тип возвращаемого опционала — допускаются только классы.

Попытка применить Force Unwrapping на нереализованном свойстве или методе приведет к точно такой же ошибке рантайма, как и применение Force Unwrapping на любом другом опционале в состоянии .none .

Nil-Coalescing

Оператор Nil-Coalescing возвращает .some -значение опционала, если опционал в состоянии .some , и значение по умолчанию, если опционал в состоянии .none . Обычно Nil-Coalescing более лаконичен, чем условие if else , и легче воспринимается, чем тернарный условный оператор ? :

Тип значения по умолчания справа должен соответствовать типу .some -значения опционала слева. Значение по умолчанию тоже может быть опционалом:

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

Optional Binding и приведение типов

Optional Binding позволяет проверить, содержит ли опционал .some -значение, и если содержит, извлечь его и предоставить к нему доступ через с помощью локальной переменной (обычно константной). Optional Binding работает в контексте конструкций if , while и guard .

В официальной документации детали реализации Optional Binding не описаны, но можно построить модель, хорошо описывающую поведение этого механизма.

В Swift каждый метод или функция без явно заданного return неявно возвращает пустой кортеж () . Оператор присваивания = является исключением и не возвращает значение, тем самым позволяя избежать случайного присвоения вместо сравнения == .

Читайте также:  Реаферон липинт способ применения

Допустим, что оператор присваивания также обычно возвращает пустой кортеж, но если правый операнд является опционалом в состоянии nil , то оператор присваивания вернет nil . Тогда эту особенность можно будет использовать в условии if , так как пустой кортеж расценивается как true, а nil расценивается как false:

Переменную, сформированную в контексте ветки true, можно будет автоматически объявить неявно извлекаемым опционалом или даже обычной переменной. Не- nil как результат операции присвоения будет являться следствием .some -состояния правого операнда. В ветке true .some -значение всегда будет успешно извлечено и «привязано» к новой переменной (поэтому механизм в целом и называется Optional Binding).

Область видимости извлеченной переменной в условии if ограничивается веткой true, что логично, поскольку в ветке false такая переменная не может быть извлечена. Тем не менее, существуют ситуации, когда нужно расширить область видимости извлеченного .some -значения, а в ветке false (опционал в состоянии .none ) завершить работу функции. В таких ситуациях удобно воспользоваться условием guard :

В Swift гарантированное компилятором приведение типов (например, повышающее приведение или указание типа литерала) выполняется с помощью оператора as . В случаях, когда компилятор не может гарантировать успешное приведение типа (например, понижающее приведение), используется либо оператор принудительного приведения as! , либо оператор опционального приведения as? . Принудительное приведение работает в стиле Force Unwrapping, т.е. в случае невозможности выполнить приведение приведет к ошибке рантайма, в то время как опциональное приведение в этом случае вернет nil :

Таким образом, оператор опционального приведения as? порождает опционал, который часто используется в связке с Optional Binding:

map и flatMap

Методы map и flatMap условно можно отнести к идиомам Swift, потому что они определены в системном перечислении Optional:

Данные методы позволяют осуществлять проверку опционала на наличие .some -значения и обрабатывать это значение в замыкании, полученном в виде параметра. Оба метода возвращают nil , если исходный опционал nil . Разница между map и flatmap заключается в возможностях замыкания-параметра: для flatMap замыкание может дополнительно возвращать nil (опционал), а для map замыкание всегда возвращает обычное значение:

Выражения с map и flatmap обычно более лаконичны, чем конструкции с предварительной проверкой в if или guard , и воспринимаются легче, чем конструкции с тернарным условным оператором:

Уместность той или иной идиомы во многом зависит от договоренностей по стилю кодирования, принятых на проекте. Вполне возможно, что явные проверка опционала и работа с извлеченным .some -значением будут выглядеть естественнее, чем применение map или flatmap :

Опционалы и обработка исключений

Swift поддерживает несколько способов обработки исключений, и один из них — это преобразование исключения в nil . Такое преобразование можно осуществить автоматически с помощью оператора try? (примеры из документации):

Обе переменные x и y являются опциональными, независимо от того, какой тип возвращает someThrowingFunction() . Таким образом, семантика поведения оператора try? такая же, как и у оператора as? . Логично также наличие оператора try! , который позволяет проигнорировать возможность выброса исключения из функции. Если исключение все же будет выброшено, то произойдет ошибка рантайма:

Опционалы и Objective-C

В Objective-C нет понятия опциональности. Лексема nil в Objective-C обозначает нулевой указатель, т.е. обращение к любой переменной ссылочного типа потенциально может вернуть nil . В Swift nil обозначает опционал в состоянии .none , неопциональная переменная не может быть nil , поэтому до Xcode 6.3 любой указатель из Objective-C транслировался в Swift как неявно извлекаемый опционал. В Xcode 6.3 в Objective-C для совместимости с семантикой опционалов были введены так называемые nullability annotations:

К ним относятся nullable (или _Nullable ), nonnull (или _Nonnull ), а также null_unspecified и null_resettable . Nullability-aннотациями могут быть обозначены ссылочные типы в свойствах, а также в параметрах и результатах функций. Помимо отдельных аннотаций можно использовать специальные макросы NS_ASSUME_NONNULL_BEGIN и NS_ASSUME_NONNULL_END для пометки участков кода целиком. Аннотации не являются полноценными модификаторами указателей или аттрибутами свойств, поскольку не влияют на компиляцию кода на Objective-C (если не считать предупреждений компиляции, например, при попытке присвоить nil свойству с аннотацией nonnull).

Аннотация null_resettable подразумевает, что сеттер свойства может принимать nil , но при этом геттер свойства вместо nil возвращает некоторое значение по умолчанию.

Трансляция из Objective-C в Swift осуществлятся по следующим правилам:

  • значения из области между NS_ASSUME_NONNULL_BEGIN и NS_ASSUME_NONNULL_END импортируются в виде неопциональных (обычных) значений;
  • значения с аннотациями nonnull или _Nonnull импортируются в виде неопциональных (обычных) значений;
  • значения с аннотациями nullable или _Nullable импортируются в виде опционалов;
  • значения с аннотацией null_resettable импортируются в виде неявно извлекаемых опционалов;
  • значения без nullability-aннотаций или аннотацией null_unspecified (аннотация по умолчанию) импортируются в виде неявно извлекаемых опционалов.

Правила передачи опционалов из Swift в Objective-C несколько проще:

  • если опционал в состоянии .none , то возвращается экземпляр NSNull;
  • если опционал в состоянии .some , то возвращается указатель на .some -значение.

Резюме, синтаксис

Лексема ! может быть использована в четырех контекстах, связанных с опциональностью:

  • для принудительного извлечения значения из опционала;
  • для объявления неявного опционала;
  • для принудительной конвертации типов в операторе as! ;
  • для принудительного подавления исключения в операторе try! .

Унарный оператор логического отрицания ! не считается, поскольку относится к другому контексту.

Лексема ? может быть использована в четырех контекстах, связанных с опциональностью:

  • для объявления явного опционала;
  • для использования опционала в optional chaining;
  • для опциональной конвертации типов в операторе as? ;
  • для преобразования исключения в nil в операторе try? .

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

Лексема ?? может быть использована в двух контекстах:

  • в Optional сhaining для обращения к опционалу второго уровня вложенности;
  • в качестве оператора Nil-Coalescing.

Заключение

Нулевой указатель — это ошибка на миллиард долларов. Вызывающая сторона все равно должна учитывать контекст и проверять результат на равенство специфичной константе, означающее отсутствие данных. Тот факт, что константа null всего одна, принципиально ситуацию не меняет и лишь добавляет неожиданностей при приведении типов.

Факт отсутствия данных должен обрабатываться отдельной сущностью, внешней по отношению к самим данным. В С++ или Java в область допустимых значений указателя включено специальный «адрес», обозначающий отсутствие адресата. «Правильный» указатель не может существовать без адресата, следовательно, не может «осознать» отсутствие адресата. Даже человеку, т.е. довольно сложной системе, приходится довольстоваться аксиомой Cogito, ergo sum (лат. — «Мыслю, следовательно существую»). У человека нет достоверных признаков собственного бытия или небытия, но у внешних по отношению к человеку сущностей эти критерии есть. В Swift такой внешней сущностью является опционал.

Дополнительные материалы

UPD: (by Alexander Zimin) Конструктор init(nilLiteral: ()) напрямую вызвать на самом деле можно:

Тем не менее, в документации от Apple не рекомендуется это делать.

Источник

Оцените статью
Разные способы