- Интервью — 10 вопросов про Swift. Часть 2
- Объясните дженерики в Swift?
- Что такое опциональные типы в swift и когда их следует использовать?
- Что такое опциональная последовательность (optional chaining) в Swift?
- Что такое принудительная распаковка (forced unwrapping)?
- Что такое неявная распаковка (implicit unwrapping)?
- Что такое опциональный биндинг (Optional binding)?
- Что такое Guard и каковы его преимущества?
- Что такое defer ?
- Перечислите, какие операторы передачи управления используются в Swift?
- Опционалы в Swift
- Что такое Optionals?
- Зачем нужны Optionals, когда есть проверка на nil?
- Как это работает?
- Идиомы использования
- Implicit unwrapping
- Optional Chaining
- Nil-Coalescing
- Optional Binding и приведение типов
- map и flatMap
- Опционалы и обработка исключений
- Опционалы и Objective-C
- Резюме, синтаксис
- Заключение
- Дополнительные материалы
Интервью — 10 вопросов про Swift. Часть 2
До запуска курса «iOS-разработчик» остается все меньше времени, поэтому сегодня мы продолжаем публиковать материал из серии «10 вопросов про Swift». Первую часть которого можно прочитать тут.
Объясните дженерики в Swift?
Дженерики (универсальные шаблоны) позволяют вам писать гибкие, многократно используемые функции и типы, которые могут работать с любым типом. Вы можете написать код, который избегает дублирования и выражает свое предназначение в ясной, абстрактной манере.
Типы Array и Dictionary в Swift являются универсальными коллекциями (дженериками).
В приведенном ниже коде универсальная функция для свапа двух значений используется для строки и целого числа. Это пример кода многократного использования.
Что такое опциональные типы в swift и когда их следует использовать?
Опциональным (Optional, “опционал”) в Swift является тип, в котором значение может быть, а может и не быть. Опционалы обозначаются путем добавления «?» к любому типу.
Варианты использования опционала:
- Фрагменты кода, которые могут претерпеть неудачу (я чего-то ожидал, но ничего не получил).
- Объекты, которые в данный момент являются пустыми, но могут стать чем-то позже (и наоборот).
Хороший пример опционала:
Свойство, которое может присутствовать или отсутствовать, например отчество или муж/жена в классе Person.
Метод, который может возвращать либо значение, либо ничего, например, поиск соответствия в массиве.
Метод, который может вернуть либо результат, либо получить ошибку и ничего не вернуть, например, пытаться прочитать содержимое файла (в результате чего обычно будут возвращены данные файла), но файл не существует.
Свойства-делегаты, которые не всегда должны быть установлены и обычно устанавливаются после инициализации.
Как слабые ссылки в классах. То, на что они указывают, может быть установлено в nil в любое время.
Если вам нужен способ узнать, когда установлено значение (данные еще не загружены> данные) вместо использования отдельного логической переменной dataLoaded.
Что такое опциональная последовательность (optional chaining) в Swift?
Процессы запроса, вызова свойств, сабскриптов и методов для опционала, который может иметь значение «nil», определяется как опциональная последовательность (опциональная цепочка).
Опциональная последовательность возвращает два значения —
- если опционал содержит “значение”, то при вызове связанных с ним свойств, методов и сабскриптов возвращается значение
- если опционал содержит “nil”, все связанные с ним свойства, методы и сабскрипты возвращают nil
Опциональная последовательность — это альтернатива принудительной распаковки.
Что такое принудительная распаковка (forced unwrapping)?
Принудительная распаковка — это способ извлечения значения, содержащегося в опционале. Эта операция опасна, потому что вы, по сути, говорите компилятору: я уверен, что этот опционал содержит реальное значение, извлеки его!
Что такое неявная распаковка (implicit unwrapping)?
Неявная распаковка: когда мы определяем неявно распакованный опционал, мы определяем контейнер, который будет автоматически выполнять принудительную распаковку каждый раз, когда мы его считываем.
Если неявно распакованный опционал равен nil и вы пытаетесь получить доступ к его упакованному значению, вы вызовете ошибку времени выполнения. Результат точно такой же, как если бы вы поместили восклицательный знак после обычного опционала, который не содержит значения.
Что такое опциональный биндинг (Optional binding)?
Вы можете распаковывать опционалы как «безопасным», так и «небезопасным» способом. Безопасный способ — использовать опциональный биндинг.
Опциональный биндинг используется для выяснения, содержит ли опционал значение, и если да, то мы сделаем это значение доступным в качестве временной константы или переменной. Таким образом нет необходимости использовать суффикс! для доступа к его значению.
Что такое Guard и каковы его преимущества?
Оператор guard прост и могуч. Он проверяет некоторое условие и, если оно оценивается как ложное, выполняется оператор else, который обычно завершает работу метода.
Преимущество guard заключается в более быстром исполнении. Guard блок выполняется только в том случае, если условие ложно, и выход из блока будет осуществлен через оператор передачи управления, такой как return , break , continue или thrown . Это обеспечивает ранний выход и меньшее количество скобок. Ранний выход означает более быстрое выполнение.
Пожалуйста, обратитесь к этой статье за дополнительной информацией.
Когда следует использовать guard let , а когда if let ?
- Используйте guard , когда вы хотите устранить неожиданный/неправильный ввод и сосредоточится на цели, если у вас есть альтернативные способы обработки ввода.
- Используйте guard if else блок чтобы уменьшить вложенность и отступы, так как он относительно компактен.
Что такое defer ?
Оператор defer используют для выполнения набора операторов непосредственно перед тем, как выполнение кода покидает текущий блок.
Оператор defer внутри блока if будет выполняться первым. Затем следует шаблон LIFO для выполнения остальных defer операторов.
Перечислите, какие операторы передачи управления используются в Swift?
break — оператор break немедленно завершает выполнение всего оператора потока управления.
continue — оператор continue указывает циклу прекратить то, что он делает, и начать заново в начале следующей итерации цикла.
return — возвращает значения из функций.
throw — нужен проброса ошибок с использованием Throwing Functions
fallthrough — оператор fallthrough используется в блоке switch case для выполнения оператора case, который находится рядом с соответствующими операторами case на основе пользовательских требований.
В swift оператор fallthrough используется для выполнения следующего case, даже если он не совпадает с исходным.
Конец второй части. Первую часть можно прочитать тут.
Ждем ваши комментарии и напоминаем о том, что уже через несколько часов пройдет день открытых дверей, в рамках которого будет подробно рассказано о нашем курсе.
Источник
Опционалы в 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 не рекомендуется это делать.
Источник