Swiftクイズ: Optionalのアンラップ方法を思いつく限り挙げてください！

アンラップ記事の第3弾(になってしまいました)。

* 第1弾: [`!`(強制アンラップ)の正体はどこに…？](https://qiita.com/YOCKOW/items/b5ede9cb28bb5d5f215d)
* 第2弾: [君は`!`より怖い強制アンラップを知っているか？](https://qiita.com/YOCKOW/items/e0d72ce485e8b30d5f24)


# はじめに

Swiftでは避けて通れない`Optional`型。値があるかもしれないし、無い(`nil`)かもしれない。
値があればその値についての何らかの処理、無ければ無かったなりの処理、と分岐することはSwiftプログラミングにおいて何度も登場することかと思います。

では、その分岐させるためのコードの書き方をいくつ知っていますか？

この記事では筆者が思いつく限りのパターンを挙げていきます(実用性の有無は問わない)。他のパターンがあればコメントしていただければ適宜追加します。


# 問

以下のコードで`maybeInt`には、50%の確率で`Int`型の何らかの値が入っています。残りの50%は`nil`です。

```Swift
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
```

`maybeInt`が`Int`型の場合はその値を、`nil`の場合は`"nilだよ。"`という文字列を表示するコードを書いてください。


# 解答集

## `!= nil`からの`!`(強制アンラップ)パターン

一番分かりやすいのは`nil`かどうかを`!=`(または`==`)演算子で比較して`if`文で分岐するというやり方でしょう。他の言語から入ってきた"Swift初心者"風の書き方です。ただ、Swiftに慣れてくると後述のoptional bindingを使うことを覚えてそちらを使うようになるかと思います。

```Swift
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
if maybeInt != nil {
  print(maybeInt!) // 強制アンラップを忘れずに
} else {
  print("nilだよ。")
}
```

### 三項条件演算子

`print`の一文で済むなら三項条件演算子を使う手もあります：

```Swift
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
maybeInt != nil ? print(maybeInt!) : print("nilだよ。")
```


## Optional Binding

Swiftには[optional binding](https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html#ID333)という書き方があります。
`if let constantName = someOptional { ... }`というように書くと、`someOptional`に値がある場合、その値が`constantName`に代入され`if`文の中身が実行されるというものです。
`!= nil`に比べて(強制アンラップを使わずに)簡潔に書くことができるという利点があります。

### `if`文のoptional binding

```Swift
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
if let definitelyInt = maybeInt {
  print(definitelyInt)
} else {
  print("nilだよ。")
}
```

### `guard`文のoptional binding

ちなみにoptional bindingは`guard`文でも使えます。その場合は、`else { ... }`で[early exit](https://docs.swift.org/swift-book/LanguageGuide/ControlFlow.html#ID525)が必要となる点に注意が必要です。

#### トップレベルでの`guard`

```Swift:トップレベルguard.swift
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
// トップレベルでearly exitをするために
// `exit`を使う
import CoreFoundation
// プラットフォームによってimport Darwinとimport Glibcを分ける書き方でも良い
guard let definitelyInt = maybeInt else {
  print("nilだよ。")
  exit(0)
}
print(definitelyInt)
```

#### クロージャを使った`guard`

```Swift:クロージャ内guard.swift
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
({ () -> Void in
  guard let definitelyInt = maybeInt else {
    print("nilだよ。")
    return
  }
  print(definitelyInt)
})()
```


## `switch`文

`nil`かどうかは`switch`文を使って判定することもできます。その`switch`文を使った書き方も何通りかあるので見ていきましょう。

### `case let _?`を使って値を取り出す

個人的に`?`を使ったこの`switch`-`case`の書き方は当初なかなか慣れなかったのですが、こういう文法もあるのだと知っておくとどこかで役に立つかもしれません。
次のコードのように`case`式で`let _?`と変数名の後に`?`をつけると`nil`ではない場合のみに文が実行されます。

```Swift
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
switch maybeInt {
case let definitelyInt?:
  print(definitelyInt)
case nil: // `default`でも良い
  print("nilだよ。")
}
```

### `Optional`が`enum`であることを利用する

`Optional<Wrapped>`は`enum`として定義されており、`case none`と`case some(Wrapped)`の二つの`case`から成ります[^optional-as-enum]。`.none`が`nil`のことです。次のように`enum`であることを明示して`switch`文を書くこともできます。

[^optional-as-enum]: https://github.com/apple/swift/blob/main/stdlib/public/core/Optional.swift

```Swift
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
switch maybeInt {
case .some(let definitelyInt):
  print(definitelyInt)
case .none:
  print("nilだよ。")
}
```


## `if`-`case`

実は`case`式は`if`でも使えます。

```Swift
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
if case let definitelyInt? = maybeInt {
  print(definitelyInt)
} else {
  print("nilだよ。")
}
```

もちろん`enum`として扱うこともできます

```Swift
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
if case .some(let definitelyInt) = maybeInt {
  print(definitelyInt)
} else {
  print("nilだよ。")
}
```


ちなみに、次のような書き方もできますが、Swiftコンパイラにバグ([SR-13904](https://bugs.swift.org/browse/SR-13904))があるため間違ったwarningが表示されます。実行に問題はありません。

```Swift
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
if case let definitelyInt as Int = maybeInt {
  print(definitelyInt)
} else {
  print("nilだよ。")
}
```

## 番外編

### `map` + `??`

アンラップそのものではありませんが…[`map`メソッド](https://developer.apple.com/documentation/swift/optional/1539476-map)と[`??`演算子](https://developer.apple.com/documentation/swift/1539917)を組み合わせて、次のように同様の動作をするコードを書くこともできます。

```Swift
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
print(maybeInt.map({ $0.description }) ?? "nilだよ。")
```

```Swift
let maybeInt: Int? = Bool.random() ? Int.random(in: .min ... .max) : nil
maybeInt.map({ print($0) }) ?? print("nilだよ。")
```



# おわりに

`Optional`のアンラップ方法はいくつもあることがお分かりいただけたでしょうか。二重三重の`Optional`(たとえば`Int??`とか`String???`とか)を扱う場合は`case`を使うほうが便利な場合があります。

この記事を読んで読者の方々のSwiftコードの書き方の幅が広がれば幸いです。