Swift 3の変更点の裏側 (アクセス制御 / @escaping)

こんにちは。VASILYのiOSエンジニアの[にこらす](https://github.com/nirma)です。

2015年の12月からSwiftがオープンソースになり、 [Swift Evolution](https://github.com/apple/swift-evolution)(Swift言語の新しい仕様について提案する場所)で多くの開発者の提案が採用されました。
今回はSwift 3の アクセス制御 と `@escaping` についての変更点と、その背景について紹介します。

Swift 言語の変更点はすべて、[Swift Evolution](https://github.com/apple/swift-evolution) で確認することができます。
さらに変更点だけでなく、決定に至る議論の内容は[Swift Evolution Mailing List](https://lists.swift.org/mailman/listinfo/swift-evolution) のメーリングリストで追うことができます。さらにここで [アーカイブ](https://lists.swift.org/pipermail/swift-evolution/) も読めます。


# アクセス制御
Swift 2 でのアクセス制御の概念は `private`, `internal`, `public` の3つでした。
Swift 3では、Swift Evolution の下記の2つの提案が承認されました。

- [Scoped Access Level SE-0025](https://github.com/apple/swift-evolution/blob/master/proposals/0025-scoped-access-level.md)
- [Allow distinguishing between public access and public overridability SE-0117](https://github.com/apple/swift-evolution/blob/master/proposals/0117-non-public-subclassable-by-default.md)

この変更でアクセスコントロールのルールに変更があり、新しいキーワード `fileprivate` と `open` が追加されました。

## `fileprivate` [SE-0025](https://github.com/apple/swift-evolution/blob/master/proposals/0025-scoped-access-level.md)
SE-0025 が承認されたことで、新しいアクセス修飾子 `fileprivate` が追加されました。
その結果、Swift 3のアクセス制御ルールは下記の表のようになりました。

|アクセス修飾子|意味|SE-0025で変更があったか|
|:--|:--|:--|
|`public`|モジュールの外からアクセス可能。クラスなら継承することもできる|変更なし|
|`internal`|同一モジュール内のならアクセス可能|変更なし|
|`fileprivate`|同一ファイル内ならアクセス可能|🆕|
|`private`|同一スコープ内のみアクセス可能|🆕|

SE-0025では `public`, `internal` の意味は変わりませんが、新しく`fileprivate`という修飾子が追加されました。
`fileprivate` として定義されたものは同じファイルの中からどこでもそのメンバーにアクセスできます。これはSwift 2での`private`と同じ挙動です。
一方 `private` の方は、同一ファイル内でも、クラスやextensionをまたぐなどスコープを超えたアクセスができなくなりました。

### なぜ `fileprivate` が必要だったのか

Swift 3から `private` の意味はファイルに対してではなく、スコープに対して `private` になりました。
`fileprivate` は Swift 2 の `private` と同じです。
`fileprivate` と `private` の区別ができると、 `extension` の中でのみ使用できるメソッドを追加出来るようになります。

例えば `Stack` というデータを保持するクラスを作るとき、push処理とpop処理 を別々の extension に書きたいことがあります。
このときpop() するときだけ、pop直前のデータを`print`したい。こういう時に extension の中で `private` が使えます。

```swift
class Stack<T> {
    fileprivate var elements = [T]()
}

// push extension
extension Stack {
    func push(_ element: T) {
        printStack()    // 呼び出せない
        elements.append(element)
    }
}

// pop extension
extension Stack {
    private func printStack() {
        print("Stack: \(elements)")
    }

    func pop() -> T? {
        printStack()
        return elements.removeLast()
    }
}
```

`printStack()` は pop extension の中だけで使いたいので、 `private` にして定義された extension の外からは呼び出せないようにできます。
`printStack()` はちゃんと隠れています。

### `private extension` と `fileprivate`
Swift の `extension` を書くときに、 `private extension` とすると、その `extension` 内のメンバーは何も書かなければ `fileprivate` と同じアクセスレベルになります。
`extension` はそもそもファイルのトップレベルに宣言するもので、 `private extension` の `private` の有効範囲はファイル全体になります。
したがって プロパティ一つ一つに `fileprivate` を書くことと同じ動作をします。
下記のコードは一緒です。

**`fileprivate`が付いてる書き方**

```swift
class Hoge { }
extension Hoge {
    fileprivate func methodA() { ... }
    fileprivate func methodB() { ... }
    fileprivate func methodC() { ... }
}
```

**暗黙的な書き方**

```swift
class Hoge { }
private extension Hoge {
    func methodA() { ... }
    func methodB() { ... }
    func methodC() { ... }
}
```

## `open` [SE-0117](https://github.com/apple/swift-evolution/blob/master/proposals/0117-non-public-subclassable-by-default.md)
SE-0117 が承認されたことで、新しいアクセス修飾子 `open` が追加されました。
その結果、Swift 3のアクセス制御ルールは下記の表のようになりました。

|アクセス修飾子|意味|SE-0117で変更があったか|
|:--|:--|:--|
|`open`|モジュールの外からアクセス可能。クラスなら継承することもできる (Swift 2 での `public` と同じ挙動)|🆕|
|`public`|モジュールの外からアクセス可能。 外部からクラスの継承はできない |🆕|
|`internal`|同一モジュール内のならアクセス可能|変更なし|
|`fileprivate`|同一ファイル内ならアクセス可能|変更なし|
|`private`|同一スコープ内のみアクセス可能|変更なし|

### なぜ `open` が必要だったのか

他の言語にある多彩なアクセスコントロールではできることが、Swift 2ではフルオープンな `public` しかありませんでした。
オープンソースライブラリを公開するときなど、クラスを公開はしても、継承はしてほしくないときに利用者に明示的にその意志を伝える事ができます。

### Swift 2 の `final public` と Swift 3 の `public`

Swift 2 の `public final` と Swift 3 の `public` は似たような振る舞いをしますが、厳密には下記のような違いがあります。

```swift
// Swift 2
public final class Mammal { }
public class Dog: Mammal { }    // 同一モジュール内でも継承できない
```

```swift
// Swift 3
public class Mammal { }
public class Dog: Mammal { }    // 同一モジュール内では継承可能
```

## `@escaping` [SE-0103](https://github.com/apple/swift-evolution/blob/master/proposals/0103-make-noescape-default.md)
Swift 3 から `@noescape` の言語キーワードが無くなり、メソッドのクロージャー引数はデフォルトで離脱しない `@noescape` と同じ挙動になりました。
一方、メソッドのクロージャー引数が離脱する「可能性がある」場合、 `@escaping` キーワードを使う必要があります。
`UIKit` にもアニメーション処理など `@escaping` が付いてるメソッドが多いので、ぜひとも理解しておきたい概念です。
( `@noescape` : 「離脱しない」、 `@escaping`: 「離脱する」と表現しています)
### 離脱しないクロージャー
Swift 3のデフォルトのクロージャー引数は、すぐに破棄されるため、`[weak hoge]` を書かなくても循環参照することはありません。

```swift
class Hoge {
    func useIt(thisClosure: () -> Void) {
        thisClosure()
    }
}
let hoge = Hoge()
// デフォルトで離脱しないクロージャーなので、[weak hoge] を書かなくても循環参照にならない。
hoge.useIt() { print(hoge) }
```

### 離脱するクロージャー実例
下記のコードのように引数のクロージャー ( `thisClosure` ) をプロパティにコピーすると、メソッド実行後にも引数のクロージャーが破棄されない (離脱する) ため、コンパイルエラーが発生します。

```swift
// コンパイルエラー
var myPrettyClosure: (() -> Void)? = nil
func trapIt(thisClosure: () -> Void) {
    thisClosure()
    myPrettyClosure = thisClosure
}
```
<img width="512" alt="myPrettyClosure.png" src="https://qiita-image-store.s3.amazonaws.com/0/67950/661fe03a-7c9e-88e3-ad39-1cc0861d9d4c.png">

クロージャーの型宣言の前に `@escaping` を書くことでコンパイルが通るようになります。

```swift
// OK
var myPrettyClosure: (() -> Void)? = nil
func trapIt(thisClosure: @escaping () -> Void) {
    thisClosure()
    myPrettyClosure = thisClosure
}
```

## なぜクロージャーのデフォルトの挙動が変わったのか
Swift 2以前のクロージャーでは、強く意識していないと容易に循環参照が発生しがちです。
Swift 3では、この循環参照が発生しにくくなるように、クロージャーはデフォルトで離脱しない ( `@noescape` ) ようになりました。
Swift でのクロージャーは、`map`, `filter`, `reduce` に代表される関数型プログラミングのような離脱しない ( `@noescape` ) 使い方の方が多く存在します。
## まとめ
今回、Swift 3のアクセス制御と `@escaping` について説明しました。
しかし、Swift 3にはまだ大きな破壊的変更が多く存在します。
`fileprivate`, `private` のおかげでもっと徹底的なモジュールアクセス制御ができるようになり、`open`, `public` のおかげで外からアクセスできるクラスが継承可能かどうかを区別できるようになりました。
次回のブログでもまた `Swift Evolution` の他の変更点に紹介したいと思います。
ー にこらす