SwiftのOptional BindingのTips

# はじめに
Swiftで開発していて`if let`や`guard let 〜 else`を使ったOptional Bindingをすることがよくあると思います

私が普段、よく使う`if let`や`guard let 〜 else`のOptional BindingのTipsを紹介してみたいと思います
（誰もが知っている当たり前のことしか書いてないかもしれませんが。。）

# Optional Bindingの基本
まずは基本として、`if let`や`guard let`を使った下記のような書き方はお馴染みだと思います

```swift
let optionalString: String? = "aaa"
if let unwrapString = optionalString {
    print(unwrapString) //aaa
}
```
これは、Optional型の変数`optionalString`を非Optional型として扱うこと(アンラップ)ができる場合、`if let`で宣言している`unwrapString`にOptional型が外れたString型の値が代入され、処理がifのスコープ内に入り、`unwrapString`変数を使うことができます

逆に`optionalString`の値がnilの場合、非Optional型として扱うことができないため、ifのスコープ内には入りません（すなわち`unwrapString`も使えません）

`if let`だとネストされるので下記のように`guard`を使って早期リターンで書くケースも多いと思います

```swift
let optionalString: String? = "aaa"
guard let unwrapString = optionalString else {
    return
}
print(unwrapString) //aaa
```

また、`optionalString`の値が空でない場合という条件を満たしたい場合は`where`を使い下記のように表現できます

```swift
let optionalString: String? = "aaa"
if let unwrapString = optionalString where !unwrapString.isEmpty {
    print(unwrapString) //aaa
}
```

`optionalString`変数を非Optional型に変換した`unwrapString`変数が空かどうかという条件を`where`に書いています
仮に`optionalString`が`nil`だった場合は、非Optional型にすることができないため、`where`に書いた条件文に入ってくることはありません


# Optional Binding Tips

さてここからがTipsとなります

* 例題としてある画像のURL文字列からUIImageを作成し、UIImageViewで表示する場合を考えてみましょう

処理の流れとしては下記とします
1.URL文字であるString型をNSURL型に変換
2.NSURL型をNSData型に変換
3.NSData型からUIImage型にしてUIImageViewを生成する

1,2,3の各型変換処理は全てOptional型が返り値になる処理になります

## Tips1
Optional型を全て羅列

下記のようにそれぞれのOptional変換を全て`if let`で羅列して、各処理の返り値の値が非Optional型として扱うことができる場合、最終的に`image`変数にUIImage型の値が生成されUIImageViewに表示することができます

```swift
let urlString = "https://pbs.twimg.com/profile_images/3734255592/0b417aa61da0196cfed35889928ee895.png"

if let url = NSURL(string: urlString), data = NSData(contentsOfURL: url), image = UIImage(data: data) {
    UIImageView(image: image)
}
```

仮に`NSURL(string: urlString)`や`NSData(contentsOfURL: url)`で非Optional型として扱えないとなった場合、そこで処理が終了しifのスコープ内には入りません
置き換えると下記と同様です

```swift
if let url = NSURL(string: urlString) {
    if let data = NSData(contentsOfURL: url) {
        if let image = UIImage(data: data) {
            UIImageView(image: image)
        }
    }
}
```

## Tips2
先の例題で、画像URLに記載されている画像の拡張子がpngである場合のみUIImageViewに表示するといった要件に変わった場合、下記のようにすることで要件を満たせます

```swift
let urlString = "https://pbs.twimg.com/profile_images/3734255592/0b417aa61da0196cfed35889928ee895.png"

if let url = NSURL(string: urlString) where url.pathExtension == "png", let data = NSData(contentsOfURL: url), image = UIImage(data: data) {
    UIImageView(image: image)
}
```


まず`if let url = NSURL(string: urlString)`で非Optional型であるNSURL型として扱える値かどうかを判定し、NSURL型が返ってきた場合、`url`変数に代入し`where`でNSURL型の拡張子を調べるメソッドを呼び出し拡張子がpngかチェックしています
その条件が通った場合、さらに`url`変数からNSData型を生成し、UIImage型を生成するといった先ほどと同じ流れになります

`where`の条件の後にOptional Bindingを使うことができるということです
ちなみに`where`の後に、letをつけないと下記のようにエラーになります

```swift
//Binding ended by previous where clause: use let to introduce a new one
if let url = NSURL(string: urlString) where url.pathExtension == "png", data = NSData(contentsOfURL: url), image = UIImage(data: data) {
    UIImageView(image: image)
}
```

## Tips3
Optional Bindingと、非Optionalな値を`if let`内で共存させたいケースってないでしょうか？
例題として、あるURL文字列を保持する`url`というOptional型の変数があり、そのファイル名の文字数が5文字より大きければ表示したいとします

処理としては下記とします
1.`url`変数が非Optionalにできるか
2.urlを`/`文字で分割
3.`/`文字で分割した最後の部分であるファイル名を取得
4.ファイル名が5文字より大きければ表示

これを直感的に下記のように書いてみます

```swift
let url: String? = "http://qiita.com/hachinobu/items/aedac203f42b215e6df4"
if let unwrapUrl = url, elements = unwrapUrl.characters.split("/"), last = elements.last where last.count > 5 {
    print(String(last))
}
```

しかしこれは、コンパイルエラーになります
`Initializer for conditional binding must have Optional type, not [SubSequence]`

`if let`や`guard let`で非Optional型と分かっているものを変数に代入することはできません
(`unwrapUrl.characters.split("/")`の返り値は非Optional型)

だからといって下記のように多重ネストしたり、分けて書いたりしたくないですよね？(`guard`を使っても2回return書いたり分けて書くことになる)

```swift
let url: String? = "http://qiita.com/hachinobu/items/aedac203f42b215e6df4"

if let unwrapUrl = url {
    let elements = unwrapUrl.characters.split("/")
    if let last = elements.last where last.count > 5 {
        print(String(last))
    }
}
```

さて、これを解決するには2つ方法があります

まず１つめは`if let`で宣言した変数に明示的に型を書いてあげれば通るようになります

```swift
let url: String? = "http://qiita.com/hachinobu/items/aedac203f42b215e6df4"
if let unwrapUrl = url, elements: [String.CharacterView] = unwrapUrl.characters.split("/"), last = elements.last where last.count > 5 {
    print(String(last)) //aedac203f42b215e6df4
}
```

`unwrapUrl.characters.split("/")`の返り値を格納する`elements`変数に`[String.CharacterView]`と型を明示的に書くことでコンパイルが通るようになります

2つめのやり方は、Optional Bindingの特性を利用して、非Optional型と分かっているものを`Optional.Some()`で包んでOptional型にしてしまうやり方です

```swift
let url: String? = "http://qiita.com/hachinobu/items/aedac203f42b215e6df4"
if let unwrapUrl = url, elements = Optional.Some(unwrapUrl.characters.split("/")), last = elements.last where last.count > 5 {
    print(String(last))
}
```

`unwrapUrl.characters.split("/")`の結果は非Optional型だと分かっているので、`Optional.Some`で包むことで強引に結果をOptional型にしてOptional Bindingのエラーをパスするやり方です

# まとめ
いかがでしょうか
Tipsというほどのものではなかったかもしれませんが、この情報が何かしら役に立てれば幸いです