【Swift 4.2】Implicitly Unwrapped Optional（IUO）が再実装されたらしいので Xcode で試した

## はじめに

Swift.org の [Reimplementation of Implicitly Unwrapped Optionals](https://swift.org/blog/iuo/) によると、Swift 4.2 で implicitly unwrapped optionals (IUOs) を再実装したみたいです。

* `!` は `ImplicitlyUnwrappedOptional` ではなく `Optional` の糖衣構文に変更
* 型としての `ImplicitlyUnwrappedOptional` は廃止
* `!` で宣言したオプショナル型は、暗黙的にアンラップできる

暗黙的にアンラップができるオプショナル型は Swift の型として表現されていましたが、これをやめて（型としては **Optional** で統一してしまって）型の末尾に `!` を付けたオプショナル型は『暗黙的にアンラップができる』という属性が付いているオプショナル型とするみたいです。

普段色々なところで使っているオプショナル型の変更なので Xcode で実際に試してみました。

### 本記事で扱わないこと

変更点や既存コードへの影響以外のことは扱わないことにします。例えば下記の内容です。

* なぜ IUO を型から属性に変更したの？
* IUO の使いどころ
* IUO ポエム

## Swift 3.0 で IUO を属性にしたのでは？

ところで、IUO を属性とする仕様は Swift 3.0 の時点で実装されたものとカン違いしていました。というのも、[Evolution proposals included in Swift 3.0](https://github.com/apple/swift-evolution/blob/master/releases/swift-3_0.md#evolution-proposals-included-in-swift-30) の実装済みプロポーザルに SE-0054（Abolish ImplicitlyUnwrappedOptional type） が含まれているからです。

実際は Swift 3.0 から Swift 4.2 で段階的に実装したみたいです。[^1] 
3.0 で『IUOを属性としたときのSwiftの書き方』を実装しておき、4.2 で `ImplicitlyUnwrappedOptional` 型を廃止したようです。

### 属性としての IUO

『IUOを属性としたときのSwiftの書き方』とは型の宣言時に IUO 属性をつけるスタイルです。イメージとしてはこんな具合です。

```swift
@_autounwrapped var foo: Int? // イメージを掴むための疑似コード
```

このように特別なオプショナル型として宣言した場合に、暗黙的にアンラップできるようにしたいのです。

IUO が型ではなく属性ならば、`ImplicitlyUnwrappedOptional` 型は不要です。この型を廃止して `!` を `Optional` 型の糖衣構文としてしまえば、擬似コードは下記のコードで実現できます。

```swift
var foo: Int! // IUO属性付きの Int? 型
```

このような宣言方法にすれば、IUO属性付きのオプショナル型は、いままでの `ImplicitlyUnwrappedOptional` 型の宣言と全く同様です。

これを実現するために Swift 3.0 では、`!` の使い所を見直しました。
下記の場所でのみ `!` を使用可能にして、それ以外の用途で使用した場合に警告を出すようにしました。

* 変数（プロパティ）
* 関数（メソッド）の引数・戻り値
* 失敗可能なイニシャライザ

また、Swift 3.0 では IUO を型ではなく、あたかも属性のように見せるために、型推論時に『暗黙的なアンラップが可能である』という情報を落として `Optional` 型に型推論するようにしました。

```swift
func maybeNone() -> Int! { return 1 }
let foo = maybeNone() // Int? 型に推論（Swift 2.2 までは Int! 型に推論）
print("\(maybeNone())") // Optional(1) を出力（Swift 2.2 までは 1 を出力）
```

このようにして Swift 3.0 以降は型としての IUO は残っていたものの、
IUOを属性のように見せることに成功しました。

### 型としての IUO は廃止予定のままだった

しかし、`ImplicitlyUnwrappedOptional` 型は Swift 4.1 まで廃止されず、`!` は `ImplicitlyUnwrappedOptional` 型の糖衣構文のままでした。

Swift 4.1 では、下記のようなコードを実行することで確認できます。

```swift

// Optional<Any> の id
print(Int(bitPattern: ObjectIdentifier(Optional<Any>.self)))

// Optional<Any> と同じ id
print(Int(bitPattern: ObjectIdentifier(Any?.self))) 

// Optional<Any> と異なる id
print(Int(bitPattern: ObjectIdentifier(ImplicitlyUnwrappedOptional<Any>.self)))

// ImplicitlyUnwrappedOptional<Any> と同じ id
print(Int(bitPattern: ObjectIdentifier(Any!.self)))

// Optional<ImplicitlyUnwrappedOptional<Int>>
print(type(of: (Int!?).none))
```

Swift 4.2 でついに `ImplicitlyUnwrappedOptional` 型が廃止されて、`!` は IUO属性付きの `Optional` 型の糖衣構文になりました。

同様のコードで Swift 4.2 の変更内容を確認できます。


```swift

// Optional<Any> の id
print(Int(bitPattern: ObjectIdentifier(Optional<Any>.self)))

// Optional<Any> と同じ id
print(Int(bitPattern: ObjectIdentifier(Any?.self)))

// Optional<Any> と同じ id
print(Int(bitPattern: ObjectIdentifier(Any!.self)))

// コンパイルエラー
// print(Int(bitPattern: ObjectIdentifier(ImplicitlyUnwrappedOptional<Any>.self)))

// Optional<Optional<Int>>
print(type(of: (Int!?).none))
```

## Swift 4.2 を試す準備

Swift 3.0 以降のプロジェクトで警告を解決していれば修正はあまりないかな？と思いつつも、Xcode を使って試してみました。

通常、Xcode 9.3 の Swiftのバージョンは 4.1（または 3.3）です。バージョン 4.2 を試すため、ツールチェーンを Xcode 標準のものから **Swift 4.2 Snapshot** に切り替えます。

その手順を示します。

### ツールチェーンのインストール

[Swift.org](https://swift.org/) からダウンロードします。

ダウンロードしたファイルでツールチェーンをインストールします。

インストーラの指示に従ってインストールします。

<img width="1680" alt="スクリーンショット 2018-04-29 14.35.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/63842/4b8a9983-1a17-79c9-8a4e-32bb4a11b360.png">


### ツールチェーンの切り替え

Xcode を開いて、メニューバーから \[**Xcode**\] → \[Preferences...\] の順に選択して環境設定を開きます。
環境設定で Components、Toolchains とタブを切り替えて、
インストールしたツールチェーンを選択します。

<img width="912" alt="スクリーンショット 2018-04-29 14.45.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/63842/401642fe-1349-88fc-ceff-1c2208f0ddb0.png">


これで Xcode 9.3 を使って、Swift 4.2 の実装済みの機能を試すことが出来ます。
不要になったツールチェーンは削除することも可能です[^2]

## Swift 4.2 を試した

### `!` が使える箇所

型の末尾に `!` をつけることで、オプショナル型にIUO属性をつけることが出来ます。
IUO属性付けられるのは下記の場所です。

* 変数（プロパティ）
* 関数（メソッド）の引数・戻り値
* 失敗可能なイニシャライザ

```swift
let bar: Int! = 3 // IUO 属性付きの Optional<Int>.some(3)
```

このように宣言をした場合、Swift 4.1 の `ImplicitlyUnwrappedOptional` 型と同様に、
メソッドやプロパティの呼び出し、関数の引数として渡す場合は、明示的なアンラップが不要になります。

```swift
bar.signum() // 1
bar.byteSwapped // 216172782113783808
Double(bar) // 3.0
```

もし `bar` に `Optional.none` が代入されている場合はプログラムは停止します。
この振る舞いも `ImplicitlyUnwrappedOptional` 型と同じです。

### `!` が使えない箇所

前述以外の箇所で使用すると警告がでます。
基本的に Swift 4.1 と同様です。

#### `as Int!`

```swift
func maybeNone() -> Int! { ... }
let foo = maybeNone() as Int! // Using '!' here is deprecated and will be removed in a future release
```

上述の例は型推論によって下記と同様になります。

```swift
let foo: Int? = maybeNone() as Int!
```

`as Int!` という表現は無意味ですので、警告を除去するには、`as Int!` の除去します。 
また、意図に合わせて宣言部分も修正も必要です。

```swift
let foo: Int = maybeNone() // 関数が .none を返すと停止する
let foo: Int? = maybeNone() // 関数が .none を返しても停止しない、Optional<Int> 
let foo: Int! = maybeNone() // 関数が .none を返しても停止しない、Optional<Int>（利用するときは暗黙的なアンラップが可能） 
let foo = maybeNone() // 型推論されて foo は Int? 型
```

関数から取得した結果を IUO 属性付きで扱いたい場合、型推論させずに明示的に型の宣言が必要ということですね。

#### その他

上述以外にも、`!` をあたかも型のように記述している場合は警告が出ます。

```swift
/* 警告が出る！ */
let arr: [Int!] = [maybeNone()]
let fn: (Int!) -> Int! = { $0 }
let type = Int!.self
```

これらは `!` を意図に合わせて `?` に置き換えるか、除去します。

### 既存コードへ影響

既存コードへの影響が全くないかと言うと、そうでもなさそうです。Swift 4.2 に変更すると `T!` は `Optional<T>` 型に変換されます。それに依る既存のコードに影響が出る可能性があります。

#### `map`

Swift 4.1 における `ImplicitlyUnwrappedOptional` 型の定義は下記です。

```swift
public enum ImplicitlyUnwrappedOptional<Wrapped> : ExpressibleByNilLiteral {
    case none
    case some(Wrapped)

    public init(_ some: Wrapped)
    public init(nilLiteral: ())
}
```

Swift 4.1 では `Optional` 型にはある `map` メソッドもありません。

Swift 4.1 から Swift 4.2 に変更すると `ImplicitlyUnwrappedOptional` は `Optional` に変換されるので、
`map` メソッドが呼べて、下記のコードは Swift 4.2 からコンパイルが通ります。

```swift
let foo: Int! = 1
let bar = foo.map { $0.signum() } // こんな使い方は、実際のプロダクトではしない。
```

そして、問題になるのは下記のようなコードです。
下記のコードは Swift 4.1 から実行できて、Swift 4.2 で実行時エラーになります。

```swift
let foo: [Any]! = [1]
let bar = foo.map { $0 as! Int } // エラー
```

Swift 4.1 には `ImplicitlyUnwrappedOptional` 型に `map` がないため、まず暗黙的にアンラップされて、 `[Any]` 型の `map` が呼び出されます。

一方で、Swift 4.2 から `[Any]!` は `Optional<[Any]>` 型のため、`Optional` の `map` を呼ぼうとします。
そうすると、`$0` は `[Any]` 型のため、Int型へキャスト出来ず、実行時にエラーになります。

暗黙的なアンラップがされないことが原因のエラーですので、解決策としては、 `foo` を `!` または `?` で明示的にアンラップすることになります。

```swift
let foo: [Any]! = [1]
let bar = foo!.map { $0 as! Int } // アンラップしたので Swift 4.2 でもOK
```

`[Any]` ではなく、 具体的な型を指定した場合は
型推論によって Array の `map` を呼び出すことが出来る場合があります。

```swift
let foo: [Int]! = [1]
let bar = foo.map { $0.signum() } // 型推論によって Array の map が呼ばれる
let bar = foo!.map { $0 } // アンラップ後に [Int] の map が呼ばれて bar は [Int] 型
let bar = foo.map { $0 } // [Int]! の map が呼ばれて bar は [Int]? 型
```

#### AnyObject ルックアップ

AnyObject 型の変数に代入したオブジェクトは、`@objc` 属性の付いたプロパティやメソッドを IUO で取得することが出来きます。 [^3]

```swift
class Cls: NSObject {
    @objc var p: Int = 0
}

let object: AnyObject = ...
let i: Int = object.p // 暗黙的にアンラップ
let j = object.p // 型推論で Int? 型
```

`@IBOutlet` や `@IBAction` も暗黙的に `@objc` 属性が追加されるので、
これらのプロパティも同様に AnyObject 型のオブジェクトからアクセスすることができます。
プロパティに `!` が付いている場合、Swift 4.2 からは ２重に IUO 属性がついた Optional 型になります。

```swift
class ViewController: UIViewController {
    @IBOutlet private weak var property: UILabel! // IUO 属性が付いた Optional 型
}

let object: AnyObject = ...
let foo: UILabel = object.property // ２回暗黙的にアンラップされて foo に代入
let bar = object.property // 型推論で IUO 属性のない UILabel?? 型の bar に代入
```

２重の Optional 型になったことで問題になるのは 
`if let` や `guard let` のようなオプショナルバインディング構文です。 

下記のようなオプショナルバインディングは１段階のアンラップしかしません。
また、 **バインディング先の変数には IUO 属性がつきません。**


```swift
if let label = a.property { // 一度アンラップされて、 IUO属性のない UILabel? にバインディング
    print(label.text) // コンパイルエラー
}
```

下記のように **宣言時に型を指定** すれば、暗黙的にアンラップさせることができます。

```swift
if let label: UILabel = a.property {
    print(label.text) // OK
}
```

#### `try?`

関数が IUO を返す場合は、`try?` も２重の Optional 型です。
オプショナルバインディングで型を指定することになります。

```swift
func test() throws -> Int! { ... }

if let x = try? test() { // エラーがスローされない場合は、IUO属性のない Int? にバインディング
    let y: Int = x // コンパイルエラー
}

if let x: Int = try? test() { // エラーがスローされない場合は、Int にバインディング
    let y: Int = x // OK
}
```

#### switch

switch でバインディングした変数の型に変更があります。

```swift
func foo(input: String!) -> String {
    switch input {
    case let output:
        return output  // コンパイルエラー
    }
}
```

Swift 4.1 では型推論で `String!` にバインディングされます。
更に `return` で暗黙的に `String` にアンラップされてコンパイル可能です。

Swift 4.2 では型推論で IUO 属性のない `String?` にバインディングされます。
`return` で暗黙的にアンラップできなくなりコンパイルエラーです。
強制アンラップするか、 `case` のパターンを修正します。

```swift
func foo(input: String!) -> String {
    switch input {
    case let output?:
        return output
    case .none:
        return "アンラップ失敗時の代わりの値"
    }
}
```

Swift 4.1 で暗黙的にアンラップして `none` の場合を無視しているので、
強制アンラップした方が、もともとのコードの意図に近いかもしれません。

```swift
func foo(input: String!) -> String {
    switch input {
    case let output:
        return output! // 暗黙アンラップできなくなったので強制アンラップする
    }
}
```

また、Swift 4.2 からは `!` で宣言しても IUO の属性のないオプショナル型の switch と変わらないので、
意図する内容にあわせて、先に `String` や `String?` の変数に代入した後に switch しても良いかもしれません。

```swift
// none の考慮が不要であればオプショナル型である必要はない
func foo(input: String) -> String {
    switch input {
    case let output:
        return output
    }
}
```

```swift
// none を考慮する必要があれば IUO 属性は不要のはず 
func foo(input: String?) -> String {
    switch input {
    case let output?:
        return output
    case .none:
        return "アンラップ失敗時の代わりの値"
    }
}
```

#### メソッドのオーバーロード

`?` と `!` でオーバーロードしている場合、
Swift 4.2 からは型として同一のためコンパイルエラーになります。

```swift

/* Swift 4.2 からコンパイルエラー */
func bar(a: inout Int?) {}
func bar(a: inout Int!) {}

/* Swift 4.2 からコンパイルエラー */
func foo(a: Int??) {}
func foo(a: Int!!) {}
```

ちなみに、下記のようなコードは Swift 4.1 でもコンパイルエラーになります。

```swift
func foo(a: Int?) {}
func foo(a: Int!) {}
```

### まとめ

* Implicitly Unwrapped Optional（IUO）が Swift4.2 で再実装された
  * `ImplicitlyUnwrappedOptional` 型は廃止
  * IUO はオプショナル型の属性に変更
* Swift 3.0 で部分的に実装は済んでいた
  * 属性としたときの書き方が出来ていない場合に警告を出すようになった
  * 事前に警告を除去していれば、Swift4.2 は大きな変更にはならない
* `!` は型としては `Optional` 型に変更
  * 既存コードへの影響はあるかもしれない

### 参考

* [Reimplementation of Implicitly Unwrapped Optionals](https://swift.org/blog/iuo/)
* [Abolish ImplicitlyUnwrappedOptional type](https://github.com/apple/swift-evolution/blob/master/proposals/0054-abolish-iuo.md#proposed-solution)

---

[^1]: [Update SE-0054 to indicate when it was fully implemented.](https://github.com/apple/swift-evolution/pull/835/files)

[^2]: [Xcodeの概要(代替ツールチェーンの活用)](https://developer.apple.com/jp/documentation/ToolsLanguages/Conceptual/Xcode_Overview/AlternativeToolchains.html#//apple_ref/doc/uid/TP40010215-CH76-SW1)

[^3]: [Accessing Objective-C Methods and Properties](https://developer.apple.com/documentation/swift/anyobject#2922815)