Swift thread 1 fatal error unexpectedly found nil while implicitly unwrapping an optional value

Swift's optionals are great, but don't unwrap them safely and you'll get the «Found nil while unwrapping an Optional value» error. How can you solve it?

Aasif Khan

By  | December 8, 2021 4:28 pm  | 4-min read

Optionals are a great feature of Swift, but if you don’t unwrap them safely you’ll run into the Unexpectedly found nil while unwrapping an Optional value error. How can you solve this error?

In this tutorial, you’ll learn about:

  • Optionals and how to use them
  • Fixing the Unexpectedly found nil while unwrapping an Optional value error
  • Safely unwrapping optionals

Understanding optionals, and fixing bugs, is an important part of iOS development. It’s critical to understand optionals as an app developer!

In Swift, Optionals are a special generic type that …

… can contain a value
… or no value at all (nil)

A variable can only be nil when it’s an optional. You declare an optional like this:

let meaningOfLife:Int? = 42

The question mark ? at the end of the type declaration denotes that the variable meaningOfLife is an optional. Therefore, this code is OK:

meaningOfLife = nil

The variable meaningOfLife is an optional, so it can be nil. The next example, however, is invalid:

let age:Int = 99
age = nil // This line is not OK

Because age isn’t an optional, it can’t be nil!

Optionals are a powerful feature of Swift, because they make your code safer and less error-prone. As a result, optionals make you more productive as a developer, because you spend less time on fixing bugs.

But… why do you need optionals?

You already know what variables are. In your app’s code, some variables can have no value at certain times:

  • When a view controller hasn’t been loaded yet, the outlet for textLabel has no value
  • When you get a key-value pair from a dictionary with subscript syntax, like items[“food”], the result can have no value, because the key “food” might not exist in the dictionary
  • When you want to access the navigation controller for a view controller with the navigationController property, it can have no value, because the view controller isn’t wrapped in a navigation controller

In Swift, an optional can either have an ordinary value, or be nil. When a variable has no value it is nil.

Almost every programming language has something similar to nil. It’s undefined in JavaScript, null in Java and NULL in PHP. In these languages, to avoid runtime errors with variables that have no value, you have to remember to check if a variable is null. You could forget that!

Swift handles optionals in a special way: it forces the developer to check whether a variable is nil, if it’s an optional. This is called unwrapping. You need to unwrap an optional before you can use it.

When you forgot to unwrap an optional, you’ll find out before your app runs, which saves you time. This makes you more productive, and your code less buggy.

Unlike other languages, Swift will check whether optionals are properly unwrapped at compile-time. So before the app runs, when the code is compiled, Swift will check every optional in your code and see if you’ve properly unwrapped it.

Unfortunately, some errors can’t be caught by the compiler. That’s when you’ll see this error:

fatal error: Unexpectedly found nil while unwrapping an Optional value
This happens while your app is running, in the following scenarios:

  • You’ve force-unwrapped an optional with !
  • An implicitly unwrapped optional is nil

In 99% of cases you’ve force-unwrapped an optional with ! and it was nil, so that’s the first place to look when you run into this error.

To summarize:

  1. Optionals are variable types that can either contain a value, or be nil
  2. You need to unwrap an optional before you can use it
  3. Because you’re forced to check whether a value is nil before running your app, you’re more productive as a developer and your code has less bugs

The most common scenario for the fatal error: Unexpectedly found nil while unwrapping an Optional value error is when you’ve force-unwrapped an optional and it was nil.

Here’s an example:

let username:String? = “Alice”
let message = “Welcome, ” + username!
print(message)
// Output: Welcome, Alice

So far so good. This is what happens in the code:

  1. You create a variable called username. It’s an optional of type String, and it’s assigned the string “Alice”
  2. You create a variable called message, and assign it the value of “Welcome” and username. The optional username is force-unwrapped with !.
  3. You print out the message, and the output is: Welcome, Alice.

Now, let’s assume that this code can run before the user has logged in. When the user hasn’t logged in, username is nil – that’s why it’s an optional.

A login form is presented to the user, but before the user could login the above code already runs. What happens?

let username:String? = nil
let message = “Welcome, ” + username!

This is what happens:

fatal error: unexpectedly found nil while unwrapping an Optional value

It’s because you force-unwrapped the optional and it was nil. You could have of course avoided running this piece of code before the user logs in. Most apps however have many moving parts that run independently, and you can’t always avoid that one piece of code runs before or after the other.

How do you solve it? Easy!

let username:String? = nil

if username != nil {
let message = “Welcome, ” + username!
print(message)
}

Before you force-unwrap username, you check with an if-statement whether username is nil. When username is nil, the code inside the squiggly brackets { } isn’t executed.

However, there’s a much more elegant way of unwrapping optionals. Read on!

Another scenario for the Unexpectedly found nil while unwrapping an Optional value error is when you’ve used implicitly unwrapped optionals.

Implicitly unwrapped optionals are optionals that you don’t have to unwrap. Like their name says they’re unwrapped automatically when you use them.

Like this:

let username:String! = “Alice”
let message = “Hello, ” + username
print(message)
// Output: Hello, Alice

There’s two things different in this example:

  • username is implicitly unwrapped with the !, after String
  • username doesn’t need to be unwrapped on the second line

Logically, when username is nil the code crashes again:

let username:String! = nil
let message = “Hello, ” + username
print(message)

This will output:

fatal error: unexpectedly found nil while unwrapping an Optional value

So why use implicitly unwrapped optionals at all?

  • Some Cocoa Touch SDKs will use implicitly unwrapped optionals. You can find out what the type is of any variable, in Xcode, by clicking on it while holding the Alt-key. Implicitly unwrapped optionals are becoming uncommon in SDKs, though.
  • Sometimes you’ve created a struct or a class, and some of its properties are nil before initializing the class, but will never be nil after. In this case, it saves you quite a bit of code (for unwrapping) if you use implicitly unwrapped optionals. This used to be a convention before Swift 3, but it’s actually equaly convenient and much safer to use optionals and optional binding (more on that later).

If you’re just starting out with coding Swift, it’s recommended to not use implicitly unwrapped optionals unless you have a specific need to do so.

In case you’ve created an implicitly unwrapped optional to avoid the pain of constantly unwrapping variables, then think again – you’re throwing away one of the safest, most powerful, and most productive features of Swift!

The best way to avoid the Unexpectedly found nil while unwrapping an Optional value error is to learn how to safely unwrap optionals.

You can use 3 ways to unwrap optionals:

  • Force-unwrapping with !
  • Optional binding with if let …
  • Optional chaining with ?

You’ve already used force-unwrapping. You’d only use force-unwrapping if you’re absolutely certain that the variable will not be nil when you’re accessing it.

If you’re not sure, use optional binding. Say you’ve coded a view controller outlet, like this:

@IBOutlet weak var textLabel:UILabel?

It’s an optional, as denoted by the ?. When you want to use it, for instance in viewDidLoad(), you can use optional binding to unwrap the optional:

if let label = textLabel {
label.text = “HOORAY!!”
}

Within the squiggly brackets a constants label is declared, and assigned the value of textLabel. This only happens when textLabel isn’t nil!

In other words, when textLabel is not nil, you’re creating a new constant label, assigning it the value of textLabel, and also executing the code within if-statement. It’s super effective!

Similarly, you can also use optional chaining. Like this:

textLabel?.text = “HOORAY!!”

This code is exactly the same as the example above, but it uses optional chaining instead of optional binding. See the ? after textLabel? When textLabel is nil, the line stops executing. You can’t access the property text on a value that’s nil, so with optional chaining you can avoid that the code continues running.

You can even create an entire chain:

let url = user?.data?.avatar?.url
print(url)
In the above example, url will be nil when either user, data or avatar is nil! You can even combine optional chaining and optional binding:

if let url = user?.data?.avatar?.url {
print(url)
}

In the above example, the url constant is only printed when none of the properties are nil.

Optionals can be tough to wrap your head around, but once you understand how to safely unwrap them, you can be sure that you’ll get this error a lot less often:

fatal error: Unexpectedly found nil while unwrapping an Optional value
Here’s what to keep in mind:

  • 99% of the time the above error is caused by a force-unwrapped optional that is nil. You can fix it by avoiding it becomes nil, which is sometimes not possible, so then you use optional binding or optional chaining.
  • Avoid using implicitly unwrapped optionals when you can, unless you have a specific need to use it.
  • Get into the habit of using optionals safely, and your code will have less bugs as a result. Use force-unwrapping, if-statements, optional binding or optional chaining to unwrap optionals.

Related Articles

  • How to Make a Brochure: Tips to Create a Brochure with free Templates
  • How to Use “where” in Swift
  • Use Appy Pie to Build Your Mobile Apps
  • What is business networking? [10 Tips to Successful Business Networking]
  • 10 Blog Title & Idea Generators for Beginners
  • The 10 Best Slack apps, Integrations and Bots to Improve Your Team’s Productivity
  • How Conversational Marketing Helps Grow Revenue?
  • How to Create a Company Page on LinkedIn to Promote Your Small Business?
  • Effective ways to Post on Instagram from your PC
  • What are The Benefits of Creating an Insurance Chatbot?

App Builder

If you have done any iOS or Mac programming in Swift, you have probably had your app crash with the following message in Xcode’s debugger:

Fatal error: Unexpectedly found nil while unwrapping an Optional value

If you don’t understand this error message, keep reading. In this article you’ll learn what this error message means, common causes of the message, and ways to fix your code so your app stops crashing.

What Does the Message Mean?

Before I can tell you what the error message means, I need to explain Swift optionals. An optional is a data type for a variable where the variable either exists or doesn’t exist, in which case it’s nil. Pretty much anything in Swift can be an optional, including integers, strings, arrays, table views, structs, and classes. The following code shows an example of declaring an optional variable:

A special kind of optional is an implicitly unwrapped optional. While an optional can either exist or not exist, an implicitly unwrapped optional must exist. If an implicitly unwrapped optional is nil and you attempt to use it, the app crashes. The following code shows an example of using an implicitly unwrapped optional:

If description exists, the code will print the description, but if description is nil, the code crashes.

Now to answer the question in the section heading. The error message Fatal error: Unexpectedly found nil while unwrapping an Optional value is saying the app is implicitly unwrapping a nil optional value. The error is similar to accessing a nil pointer in C, C++, or Objective-C.

What Causes the Error?

The general cause of the error is having an implicitly unwrapped optional that’s nil. But you’re looking for specific causes. I can think of three common sources of implicitly unwrapped optionals that are nil.

The first common source is forgetting to connect the outlets for your user interface elements. Outlets in Interface Builder are usually declared as implicitly unwrapped optionals because you can assume the outlet is going to exist after loading the storyboard or xib file. But if you forget to connect the outlet, the outlet is nil and your app will crash when it tries to use the outlet. The following screenshot shows what a disconnected outlet looks like in Xcode:

DisconnectedOutlet

The disconnected outlet is the circle above Line 14. A connected outlet has a filled-in circle.

The second common source is using as! to downcast to a specific type. Look at the following code to instantiate a view controller from a storyboard:

let documentViewController = storyBoard.instantiateViewController(

    withIdentifier: «DocumentViewController»)

    as! DocumentViewController

This code will crash in any of the following situations:

  • I forgot to give the view controller an identifier in the storyboard.
  • The identifier in the storyboard doesn’t match the string in the code.
  • I forgot to set the class of the view controller in the storyboard.

With implicitly unwrapped optionals it doesn’t take much to cause a crash.

The last common source is using implicitly unwrapped optionals in your code. If you see an exclamation point at the end of any of your variable names, you have a potential crash in your code. What doesn’t help matters is when you have a compiler syntax error in your code involving optionals, Xcode’s suggested fix is to make an implicitly unwrapped optional. Following Xcode’s advice makes your code more likely to crash.

How Do You Fix the Error?

The general fix to avoid these crashes is to avoid using implicitly unwrapped optionals. The following techniques will help you avoid crashes caused by implicitly unwrapped optionals:

  • Connect your user interface elements to the outlets in your code.
  • Use as? instead of as! when downcasting.
  • Use if let or guard statements to safely unwrap your optionals.
  • Use the ?? operator to provide a default value in case the optional value is nil.

To connect your user interface elements to your outlets, open Xcode’s assistant editor so your source code file and your storyboard or xib file are both open. Select the user interface element in the storyboard or xib file. Hold the Control key down and drag to the outlet in your code to make the connection.

I can demonstrate the last two techniques by fixing the earlier example of instantiating a view controller from the storyboard. Using an if let statement and as? to downcast is enough to fix the code. The following code makes sure the view controller has been instantiated, then presents the view controller:

if let documentViewController = storyBoard.instantiateViewController(

    withIdentifier: «DocumentViewController»)

    as? DocumentViewController {

    present(documentViewController, animated: true, completion: nil)

}

Now if I forgot to give the view controller an identifier or misspelled the name of the identifier, the app won’t crash. The app won’t present the view controller, but at least there won’t be a crash.

Look at this:

Code Block 
class ViewController: UIViewController, UITextFieldDelegate {
@IBOutlet weak var weightInput: UITextField!
@IBOutlet weak var heightInput: UITextField!
@IBOutlet weak var BMIOutput: UITextField!
@IBOutlet weak var categoryOutput: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.weightInput.delegate = self //added either ! or ? here and don't get fatal error
self.heightInput.delegate = self //added either ! or ? here and don't get fatal error
}

Line 2 you declare an IBOutlet, as implicitly unwrap.
That means that weightInput is an optional, so either nil or some value.
Implicitly unwrap means that if you call it once it has a value, you don’t need to unwrap anymore. It is done implicitly.
But, if you call when it is not initialized (nil) and try to unwrap, of course you crash.

And an IBOutlet is loaded and initialized when the view is loaded. But the IBOulet var is initialized only if it is properly connected to the object in storyboard. Otherwise, no way to know which var to initilize, hence it remains nil.

So most likely in your case, the connection between th IBOutlet and the TextField in storyboard is not set or is broken.
Then, when you call line 10:
self.weightInput.delegate = self

Do you see a black dot on the left of

Code Block 
@IBOutlet weak var weightInput: UITextField!

If not, control drag from the white circle to the textField in storyboad.
The connection will be made, and crash will disappear (if you do it for all IBOutlets).

Note: you wrote:

The code builds in both cases and only produces a Fatal Error when I run the simulator without the unwrapping tags.
With the unwrapping added the simulator runs, but when I enter the data and click on the link nothing appears in the MY BMI text box.

It is logical not to crash with ?, but a bit surprising you do not crash with !
If weightInput is nil, when you set with optional chaining

Code Block 
self.weightInput?.delegate = self

in fact it does nothing (execution «halted» after ? because of nil).
TextField appear on screen, you can type, but delegate functions (as textFieldShouldReturn) are not called. Nothing appears in the MY BMI text

Swift optional variable is a variable that can have nil ( same to null ) value. And you do not need to initialize an optional variable before using it. If not initialized, the optional variable value is nil. It does not like none optional variable which you must assign an initial value before using it.

1. How To Declare A Swift Optional Variable.

  1. To declare an optional variable, you need to add a question mark ( ? ) at the end of variable declaration like below.
    var x:Int?
  2. If you do not initialize an optional variable with a value, then its value is nil. If you want to use its value later, you should add an exclamation mark ( ! ) after the variable name to unwrap it.
    x!
  3. You can read the article How To Fix Error ‘Value Of Optional Type Must Be Unwrapped To A Value Of Type’ In Swift to learn more about swift optional variable usage.

2. How To Fix Fatal Error: Unexpectedly Found Nil While Unwrapping An Optional Value.

  1. In my example project, I meet an error message like this, Fatal error: Unexpectedly found nil while unwrapping an Optional value. Just as the error message said, one variable value is nil in my swift source code, and I use that nil variable in my swift source code later.
  2. So to resolve this kind of error, you had better set a breakpoint at the code line that triggers the error like the below picture, and then debug your source code to see why the variable value is nil. You can read the article How To Debug And Get Error Messages In Xcode Step By Step to learn more.
    variable-value-is-nil-in-xcode-debug-mode

3. The Reason For Fatal Error: Unexpectedly Found Nil While Unwrapping An Optional Value.

  1. After debug and investigate, I finally find the reason that triggers the fatal error in my project.
  2. The reason for variable imageIcon‘s value is nil is because the variable loaded image has been removed from the hard disk. Below are the steps that cause the error.
  3. First, I add two image files under the Xcode project.
    first-add-two-images-under-project-
  4. But later I move the two images from the project to the under-level project folder. But the two images still exist under the Xcode project, so I want to delete them. I select and right-click the two images and then click Delete menu item in the popup menu list to remove them.
  5. Then it will popup below confirm dialog, there are two buttons that I can choose, one is Remove References the other is Move to Trash. The Remove References button will only remove the image file references from the Xcode project, and the image file still exists. The Move to Trash button will remove the two image files permanently from the hard disk. I click the Move to Trash button, and this cause the error occurred, the imageIcon variable load a not exist image, so imageIcon variable’s value is nil. The correct action is to click the Remove References button to remove the image reference from the Xcode project but not remove image files from the hard disk.
    click-the-move-to-trash-button-in-xcode-project

Несмотря на некоторый опыт в мобильной разработке (в том числе с применением 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) переменной, которая не является опционалом.

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

public enum Optional<Wrapped> : ExpressibleByNilLiteral {

    /// The absence of a value.
    ///
    /// In code, the absence of a value is typically written using the `nil`
    /// literal rather than the explicit `.none` enumeration case.
    case none

    /// The presence of a value, stored as `Wrapped`.
    case some(Wrapped)

    /// Creates an instance that stores the given value.
    public init(_ some: Wrapped)

        ...

    /// Creates an instance initialized with `nil`.
    ///
    /// Do not call this initializer directly. It is used by the compiler
    // when you initialize an `Optional` instance with a `nil` literal.
    public init(nilLiteral: ())

        ...

}

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

var my_variable: Int?

var my_variable: Optional<Int>

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

var my_variable: Int? = nil

var my_variable: Optional<Int> = Optional.none

var my_variable = Optional<Int>.none

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

var my_variable = Optional(42) // тип .some-значения Int опеределен неявно

var my_variable = Optional<Int>(42) // явное указание типа Int для наглядности

var my_variable = Int?(42) // тип Int необходимо указать явно

var my_variable: Int? = 42 // тип Int необходимо указать явно

var my_variable = Optional.some(42) // тип Int опеределен неявно

var my_variable = Optional<Int>.some(42) // явное указание типа для наглядности

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

public protocol ExpressibleByNilLiteral {

    /// Creates an instance initialized with `nil`.
    public init(nilLiteral: ())
}

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

var test = Optional<Int>(nilLiteral: ()) // не рекомендуется

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

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

var my_variable: Int? = nil // или var my_variable: Int? = Optional.none

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

var my_variable: Int?

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

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

public enum Optional<Wrapped> : ExpressibleByNilLiteral {

        ...

    /// The wrapped value of this instance, unwrapped without checking whether
    /// the instance is `nil`.
    public var unsafelyUnwrapped: Wrapped { get }
}

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

Подробнее

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

_fatal error: unsafelyUnwrapped of nil optional_

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

_fatal error: unexpectedly found nil while unwrapping an Optional value_

let my_variable1 = Int?(42) // содержит 42, тип Optional Int
let my_value1A = my_variable1! // содержит 42, тип Int
let my_value1B = my_variable1.unsafelyUnwrapped // содержит 42, тип Int

let my_variable2 = Int?.none // содержит nil, тип Optional Int
let my_value2A = my_variable2! // ошибка рантайма

// ошибка рантайма в режиме -Onone, НЕОПРЕДЕЛЕННОЕ ПОВЕДЕНИЕ в режиме -O
let my_value2B = my_variable2.unsafelyUnwrapped

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

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

Implicit unwrapping

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

// getOptionalResult() может вернуть nil
let my_variable: Int? = getOptionalResult() // тип Optional Int
if my_variable != nil {
  // my_value получит .some-значение опционала из getOptionalResult()
  let my_value = my_variable!
} else {
  // ошибка рантайма
  let my_value = my_variable!
}

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

let my_variable1: Int? = 42 // тип Optional Int
let my_variable2: Int! = 42 // тип Implicitly Unwrapped Optional Int
var my_variable3: Int! = 42 // тип Implicitly Unwrapped Optional Int

...

my_variable3 = nil // где-то непредвиденно присвоен nil

...

func sayHello(times:Int) {
    for _ in 0...times {
        print("Hello!")
    }
}

sayHello(times: my_variable1!) // обязаны извлекать значение явно
sayHello(times: my_variable1) // ошибка компиляции
sayHello(times: my_variable2!) // можем, но не обязаны извлекать значение явно
sayHello(times: my_variable2) // неявное извлечение
sayHello(times: my_variable3) // ошибка рантайма

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

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

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

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

class Country {
    let name: String
    var capitalCity: City!
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
}

class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

var country = Country(name: "Canada", capitalName: "Ottawa")
print("(country.name)'s capital city is called (country.capitalCity.name)")
// Prints "Canada's capital city is called Ottawa"

В этом примере экземпляры классов 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-значению возвращаемого опционала).

// цепочка из трех звеньев: первое звено — опционал `country.mainSeaport?`,
country.mainSeaport?.nearestVacantPier?.capacity

// ошибка компиляции, после `?` должно быть следующее звено
let mainSeaport = country.mainSeaport?

// цепочка вернет `nil` на первом звене
country = Country(name: "Mongolia")
let capacity = country.mainSeaport?.mainPier?.capacity

// цепочка вернет опционал — ближайший незанятый пирс в Хельсинки
country = Country(name: "Finland")
let nearestVacantPier = country.mainSeaport?.nearestVacantPier

// цепочка вернет опционал — количество свободных мест, даже если capacity
// является неопциональным значением
country = Country(name: "Finland")
let capacity = country.mainSeaport?.nearestVacantPier?.capacity

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

let valueA = 42
let optionalValueA = Optional(valueA)
let doubleOptionalValueA = Optional(optionalValueA)
let tripleOptionalValueA = Optional(doubleOptionalValueA)

let tripleOptionalValueB: Int??? = 42 // три `?` означают тройную вложенность
let doubleOptionalValueB = tripleOptionalValueB!
let optionalValueB = doubleOptionalValueB!
let valueB = optionalValueB!

print("(valueA)") // 42
print("(optionalValueA)") // Optional(42)
print("(doubleOptionalValueA)") // Optional(Optional(42))
print("(tripleOptionalValueA)") // Optional(Optional(Optional(42)))

print("(tripleOptionalValueB)") // Optional(Optional(Optional(42)))
print("(doubleOptionalValueB)") // Optional(Optional(42))
print("(optionalValueB)") // Optional(42)
print("(valueB)") // 42

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

let optionalAppDelegate = UIApplication.shared.delegate
let doubleOptionalWindow = UIApplication.shared.delegate?.window
let optionalFrame = UIApplication.shared.delegate?.window??.frame // два '?'
print("(optionalAppDelegate)") // Optional( ... )
print("(doubleOptionalWindow)") // Optional(Optional( ... ))
print("(optionalFrame)") // Optional( ... )

Примечание

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

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

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

public protocol UIApplicationDelegate : NSObjectProtocol {

...

@available(iOS 5.0, * )
optional public var window: UIWindow? { get set } // модификатор optional

...

}

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

@objc
public protocol myOptionalProtocol {

    @objc
    optional var my_variable: Int { get }

    @objc
    optional var my_optionalVariableA: Int? { get } // ошибка компиляции:
    // модификатор @objc не применим к опционалу типа Int?, т.к. Int
    // не является классом

    @objc
    optional var my_optionalVariableB: UIView? { get }

    @objc
    optional func my_func() -> Int

    @objc
    optional func my_optionalResultfuncA() -> Int? // ошибка компиляции:
    // модификатор @objc не применим к опционалу типа Int?, т.к. Int
    // не является классом

    @objc
    optional func my_optionalResultfuncB() -> UIView?

    @objc
    optional init(value: Int) // ошибка компиляции:
    // модификатор optional не применим инициализаторам

}

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, myOptionalProtocol {

    var window: UIWindow?

    func application(_ application: UIApplication,
      didFinishLaunchingWithOptions launchOptions:
      [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        let protocolAdoption = self as myOptionalProtocol

        // Optional<Int>
        print("(type(of: protocolAdoption.my_variable))")

        // Optional<Optional<UIView>>
        print("(type(of: protocolAdoption.my_optionalVariableB))")

        // Optional<() -> Int>
        print("(type(of: protocolAdoption.my_func))")

        // Optional<Int>
        print("(type(of: protocolAdoption.my_func?()))")

        // Optional<() -> Optional<UIView>>
        print("(type(of: protocolAdoption.my_optionalResultfuncB))")

        // Optional<UIView>
        print("(type(of: protocolAdoption.my_optionalResultfuncB?()))")

        return true
    }
}

Для опциональных @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, и легче воспринимается, чем тернарный условный оператор ?:

let optionalText: String? = tryExtractText()

// многословно
let textA: String
if optionalText != nil {
    textA = optionalText!
} else {
    textA = "Extraction Error!"
}

// в одну строку, но заставляет думать
let textB = (optionalText != nil) ? optionalText! : "Extraction Error!"

// кратко и легко воспринимать
let textC = optionalText ?? "Extraction Error!"

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

let optionalText: String?? = tryExtractOptionalText()
let a = optionalText ?? Optional("Extraction Error!")

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

let wayA: Int? = doSomething()
let wayB: Int? = doNothing()
let defaultWay: Int = ignoreEverything()

let whatDo = wayA ?? wayB ?? defaultWay

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

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

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

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

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

var my_optionalVariable: Int? = 42

// условие истинно, my_variable "привязана" к .some-значению my_optionalVariable
if let my_variable = my_optionalVariable {
    print("(my_variable)") // 42
}

my_optionalVariable = nil

// условие ложно, my_variable не создана
if let my_variable = my_optionalVariable {
    print("(my_variable)")
} else {
    print("Optional variable is nil!") // Optional variable is nil!
}

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

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

let my_optionalVariable: Int? = extractOptionalValue()

// чересчур многословно
let my_variableA: Int
if let value = my_optionalVariable {
  my_variableA = value
} else {
  return
}
print(my_variableA + 1)

// лаконично
guard let my_variableB = my_optionalVariable else {
  return
}
print(my_variableB + 1)

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

class Shape {}
class Circle: Shape {}
class Triangle: Shape {}

let circle = Circle()
let circleShape: Shape = Circle()
let triangleShape: Shape = Triangle()

circle as Shape // гарантированное приведение
42 as Float // гарантированное приведение
circleShape as Circle // ошибка компиляции

circleShape as! Circle // circle
triangleShape as! Circle // ошибка рантайма

circleShape as? Circle // Optional<Circle>
triangleShape as? Circle // nil

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

class Shape {}
class Circle: Shape {}
class Triangle: Shape {}

let circleShape: Shape = Circle()
let triangleShape: Shape = Triangle()

// условие истинно, успешное приведение
if let circle = circleShape as? Circle {
    print("Cast success: (type(of: circle))") // Cast success: (Circle #1)
} else {
    print("Cast failure")
}

// условие ложно, приведение не удалось
if let circle = triangleShape as? Circle {
    print("Cast success: (type(of: circle))")
} else {
    print("Cast failure") // Cast failure
}

map и flatMap

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

public enum Optional<Wrapped> : ExpressibleByNilLiteral {

    ...

    /// Evaluates the given closure when this `Optional` instance is not `nil`,
    /// passing the unwrapped value as a parameter.
    ///
    /// Use the `map` method with a closure that returns a nonoptional value.
    public func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?

    /// Evaluates the given closure when this `Optional` instance is not `nil`,
    /// passing the unwrapped value as a parameter.
    ///
    /// Use the `flatMap` method with a closure that returns an optional value.
    public func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U?

        ...

}

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

let my_variable: Int? = 4

let my_squareVariable = my_variable.map { v in
    return v * v
}

print("(my_squareVariable)") // Optional(16)

let my_reciprocalVariable: Double? = my_variable.flatMap { v in
    if v == 0 {
        return nil
    }
    return 1.0 / Double(v)
}

print("(my_reciprocalVariable)") // Optional(0.25)

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

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd MMM yyyy"
let date: Date? = extractOptionalDate()

// многословно
let dateStringA: String
if date != nil {
    dateStringA = dateFormatter.string(from: date!)
} else {
    dateStringA = "Unknown date"
}

// в одну строку, но сложно воспринимать
let dateStringB =
(date == nil ? nil : dateFormatter.string(from: date!)) ?? "Unknown date"

// лаконично, с непривычки сложно воспринимать (если map используется редко)
let dateStringC = date.map(dateFormatter.string) ?? "Unknown date"

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

// отдельно Optional Binding и явный вызов метода у извлеченного объекта
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
  if let cell = sender as? UITableViewCell,
    let indexPath = tableView.indexPathForCell(cell) {
      let item = items[indexPath.row]
  }
}

// В одном вызове 3 этапа:
// 1) опционал как результат приведения типов;
// 2) передача неявного замыкания в метод flatMap полученного опционала;
// 3) Optional Binding к результату flatMap.
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
  if let indexPath =
    (sender as? UITableViewCell).flatMap(tableView.indexPathForCell) {
      let item = items[indexPath.row]
  }
}

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

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

func someThrowingFunction() throws -> Int {
    // ...
}

// многословно
let y: Int?
do {
    y = try someThrowingFunction()
} catch {
    y = nil
}

// лаконично
let x = try? someThrowingFunction()

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

// ошибка рантайма, если loadImage будет выброшено исключение
// photo не является опционалом (в отличие от x и y)
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")

Опционалы и 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:

@interface myObject : NSObject

@property (copy, readonly) NSArray * _Nullable myValuesA;
@property (copy, readonly) NSString * _Nonnull myStringA;

@property (copy, readonly, nullable) NSArray * myValuesB;
@property (copy, readonly, nonnull) NSString * myStringB;

@end

К ним относятся 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 такой внешней сущностью является опционал.

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

  • Generic Enumeration: Optional (developer.apple.com)
  • Instance Property: unsafelyUnwrapped (developer.apple.com)
  • Swift Language Guide: Optionals (developer.apple.com)
  • Unowned References and Implicitly Unwrapped Optional Properties (developer.apple.com)
  • Nil-Coalescing Operator (developer.apple.com)
  • Optional Chaining (developer.apple.com)
  • Optional Protocol Requirements (developer.apple.com)
  • The as! Operator (developer.apple.com)
  • Converting Errors to Optional Values (developer.apple.com)
  • Nullability and Objective-C (developer.apple.com)
  • Nullability and Optionals (developer.apple.com)
  • Xcode 6.3 Release Notes: Objective-C Language Enhancements (developer.apple.com)
  • Option type (en.wikipedia.org)
  • Nullable type (en.wikipedia.org)
  • Re-implementing Optionals using Swift’s powerful enum type (jamesonquave.com)

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

var test = Optional<Int>(nilLiteral: ())

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

Oct 11, 2021 6:17 AM in response to deborah_Snp

From the swift docs:

The nil-coalescing operator (a ?? b) unwraps an optional a if it contains a value, or returns a default value b if a is nil. The expression a is always of an optional type. The expression b must match the type that’s stored inside a.

The nil-coalescing operator is shorthand for the code below:

  1. a != nil ? a! : b

The code above uses the ternary conditional operator and forced unwrapping (a!) to access the value wrapped inside a when a isn’t nil, and to return b

otherwise. The nil-coalescing operator provides a more elegant way to

encapsulate this conditional checking and unwrapping in a concise and

readable form.

Note

If the value of a is non-nil, the value of b isn’t evaluated. This is known as short-circuit evaluation.

The example below uses the nil-coalescing operator to choose between a

default color name and an optional user-defined color name:

  1. let defaultColorName = «red»
  2. var userDefinedColorName: String? // defaults to nil
  3. var colorNameToUse = userDefinedColorName ?? defaultColorName
  4. // userDefinedColorName is nil, so colorNameToUse is set to the default of «red»

Oct 11, 2021 12:00 PM in response to deborah_Snp

The first thing I would do is change the jsonResult variable to something else, such as jsonData.

if let jsonData = jsonResult {
    let weather_array = jsonData["weather"] as! NSArray
    // Rest of code here
}

The second thing I would do is set a breakpoint on the let weather_array line of code. Xcode will pause your app when it reaches that line of code. When the breakpoint is hit, type the following in the debug console:

po jsonData["weather"]

The value of the weather data will appear in the console. The probable cause of your crash is that the weather data is not an array. In your code, you are using as! NSArray to convert the json data to NSArray. The app is unable to make this conversion so the app crashes.

The following articles provide more detailed information on Xcode’s debugger and Swift optionals:

  • An Introduction to Xcode’s Debugger
  • Crashing with Swift Optionals

Oct 11, 2021 6:29 PM in response to deborah_Snp

Don’t use the «!» operator. The whole point of Swift is to be a «safe» programming language. In theory, it is so much better than C because C lets you do dangerous things. Well, so does Swift. If you don’t know how and when it is safe to use those dangerous operators, then don’t use them.

fatal error: unexpectedly found nil while unwrapping an Optional value

This is a super common error you’ll encounter when developing in Swift. We’ll explain the solution and why this happens in this article.

What Does Unexpectedly Found Nil While Unwrapping An Optional Value Mean

This error occurs when you unwrap an Optional with a nil value. This usually happens when you use the ! operator.

What Are Optionals

To understand this error, we first must understand what Optionals in Swift are.
Optionals are a special generic type in the Swift Programming language. They are can contain a value or no value.

When an optional has no value, it is considered nil. You can declare an optional by using the question mark ? after the type.

For example:

In the case above mightBeNil can either contain a string or no value (nil). Optionals can be of any type. Here are some more examples:

var optionalInt: Int?
var optionalCustomClass: CustomClass?
var optionalBool: Bool?
var optionalDouble: Double?

Now that we know what optionals are, let’s find out how unexpectedly found nil occurs.

Force Unwrapping Optionals

Force unwrapping optionals is the reason why this error happens, almost 99% of the time. Let’s take a look at a case that works and another one that doesn’t.

var greeting: String?
greeting = "Hello and welcome"
print(greeting! + " friend")

The above block of code works fine and will print “Hello and welcome friend” to your console.
Try running this code instead, removing the middle line where we set the variable.

var greeting: String?
print(greeting! + " friend")

Xcode will print this out in console:

Fatal error: Unexpectedly found nil while unwrapping an Optional value

In order to access the greeting variable, we decided to forcefully unwrap it (with the ! operator).
You can’t add a nil and a String type together, strings can only be added with other strings.

Since the greeting variable has no value assigned to it and is nil, Xcode will throw this error when trying to add greeting! and ” friend”.

Implicitly Unwrapped Optionals

This is the other way this error can occur. This happens often with IBOutlets, since they’re usually implicitly unwrapped optionals.

Implicitly unwrapped optionals are defined with a ! operator after their type:

These optionals are assumed to always contain a value. When you access an implicitly unwrapped optional, it will be forced unwrapped automatically.

So if we do:

var greeting: String!
print(greeting + " friend")

We’ll see our error again. You should try to avoid implicitly unwrapped optionals as much as you can.

Why Use Optionals

This seems confusing! Why do we need optionals anyways? Why can’t we just get rid of them entirely? We need optionals as there are times in your code where certain variables simply have no value at a given time. For example:

  • Accessing dictionary values such as person["address"], the key address may not have a value assigned to it yet.
  • User input variables, before the user has entered their information
  • Variables that are going to be set by an API call, before the API call has completed.

There are many other reasons why you would need optionals. Every language has some form of a nil variable, in Java its called null.
The problem with other languages is that they crash at runtime when a nil value is found. Optionals help us catch these bugs at compile time, preventing a crash. They force the developer to think and unwrap the variable every time.

So why do these crashes still happen in Swift? Why aren’t they caught by the compiler? It’s because the two methods mentioned above: Force Unwrapping (!) and Implicitly Unwrapped Optionals can’t be caught by the compiler.

You should try avoid force unwrapping and implicitly unwrapped optionals as much as you can. Let’s learn how to use optionals correctly and safely.

How To Safely Unwrap Optionals

You could do it by checking for nil, like in many other languages. For example:

var greeting: String?
if greeting != nil {
    print(greeting! + " friend")
}

However, this still involves force unwrapping and using the ! operator. A more Swift approach would be to use optional binding.

Optional Binding

Optional binding checks first if the optional has a value and if it does, assigns it to a non-optional constant. This pattern is used quite often in Swift development.

Here’s an example:

var greeting: String?
if let unwrappedGreeting = greeting {
    print(unwrappedGreeting + " friend")
} else {
    print("greeting has no value!")
}

Notice in this pattern we don’t use any force unwraps (!). You can also chain this together with multiple optionals:

var greeting: String?
var friendsName: String?
if let unwrappedGreeting = greeting, let unwrappedFriendsName = friendsName {
    print(unwrappedGreeting + " " + unwrappedFriendsName)
} else {
    print("greeting or friend has no value!")
}

This above code will print greeting or friend has no value!. However, if we assign values to both of those valuables like so:

var greeting: String? = "Hey"
var friendsName: String? = "Eddie"
if let unwrappedGreeting = greeting, let unwrappedFriendsName = friendsName {
    print(unwrappedGreeting + " " + unwrappedFriendsName)
} else {
    print("greeting or friend has no value!")
}

It will print:

Guard Statements

What if you want to use this variable outside of an if statement? Do you have to continually wrap it in if statements? Nope! There’s a better solution, it’s called a guard statement.

Guard statements define a condition for the code to continue, otherwise it must return. You can use this in combination with optional binding like so:

printGreeting()

func printGreeting() {
    var greeting: String?
    var friendsName: String?

    guard let unwrappedGreeting = greeting, let unwrappedFriendsName = friendsName else {
        print("greeting or friend has no value!")
        return
    }

    print(unwrappedGreeting + " " + unwrappedFriendsName)
}

I use guard statements quite frequently when I’m writing Swift code.

Nil Coalescing Operator

The nil coalescing operator is shorthand if statement. It lets you define a default value if the optional contains nil.

var greeting: String?
var friendsName: String?

print((greeting ?? "Hi") + " " + (friendsName ?? "friend"))

In our above example, if no value is provided for greeting or friend, it will print the “Hi friend”.

Optional Chaining

Optional chaining lets you access a property or a function of an optional by simply using the ? operator.

For example, if you call uppercased() on a String:

var str: String?
str?.uppercased()

Even though str is nil in the above example, your code will not crash. It will simply not make that function call to uppercased() if str is nil.

Of course, you can chain these together, let’s pretend that middleName is an optional property of an optional person variable and that we’d like to get the uppercase string of that.

person?.middleName?.uppercased()

Optional chaining can make your code cleaner and remove the need for complicated if statement structures.

If you liked this post and want to learn more, check out The Complete iOS Developer Bootcamp. Speed up your learning curve — hundreds of students have already joined. Thanks for reading!

Понравилась статья? Поделить с друзьями:
  • Swift network error
  • Swift init error
  • Swift error localizeddescription
  • Swift error code z117
  • Swift decoding error