Swiftの @escaping と weak/unowned の理解


Swiftでクロージャを使うコードを書いていると，よく `@escaping` とか `weak` / `unowned` といったワードを目にすることがあります．

なんとなく「コンパイラに怒られたら付ける」とか「参考にしたコードに書いてあったから付ける」などとしてしまっていたのですが（反省），ちゃんと意味を調べてみてもなかなか理解ができませんでした．

そこで，参考にした記事と私が動作確認したコードを示し，「こう考えたら私は理解できた」というメモを残しておきます．

なお，概念の理解を優先しているため，用語や表現の正確性は無いかもしれません．

## 参考にした記事

* [Swift 3 の @escaping とは何か - Qiita](http://qiita.com/mishimay/items/1232dbfe8208e77ed10e)
* [&quot;Weak, Strong, Unowned, Oh My!&quot; - A Guide to References in Swift — KrakenDev](https://krakendev.io/blog/weak-and-unowned-references-in-swift)

## 環境

* Swift 3.1
* Xcode 8.3.3

## escaping の理解

まず， `@escaping` の文法上の出現位置は，関数定義のパラメータリスト中におけるクロージャの型定義の前です．

```swift:
class Foo {
  func foo(_ closure: @escaping () -> Void) {
  }
}
```

### escape＝退避＝非同期実行 と考えてみる

この `@escaping` が付いているということは，

* 渡すクロージャが退避（関数のスコープ外に保管（コピー））される
* 渡すクロージャが関数（ `foo` ）の実行完了後に呼ばれる（関数実行後も生存し続ける）
* 渡すクロージャが非同期実行される

と考えることができます．

### escaping が必要ない場合

```swift:
class Foo {
  func foo(_ closure: () -> Void) {
    closure()
  }
}

class Bar {
  func bar() {
    let foo = Foo()
    foo.foo {
      print("closure called")
    }
    print("Foo.foo() called")
  }
}

let bar = Bar()
bar.bar()

// --> closure called
// --> Foo.foo() called

```

`Foo.foo(_ closure:)` には `@escaping` を付けていません．
これにより渡されるクロージャ（ `{ print("closure called") }` ）はescapeされません（＝退避されません．関数実行完了前に呼ばれます．非同期実行されません．）

`Array.forEach(_:)` の定義（ `func forEach(_ body: (Element) throws -> Void) rethrows` ）を見るとわかるように，関数の実行完了前にクロージャが呼ばれ，保管されないような関数には `@escaping` は付いていません．


### escaping が必要な場合

```swift:
class Foo {
  var storedClosure: (() -> Void)?

  func foo(_ closure: @escaping () -> Void) {
    storedClosure = closure
  }

  func callClosure() {
    storedClosure?()
  }
}

class Bar {
  func bar() {
    let foo = Foo()
    foo.foo {
      print("closure called")
    }
    print("Foo.foo() called")
    foo.callClosure()
  }
}

let bar = Bar()
bar.bar()

// --> Foo.foo() called
// --> closure called
```

`Foo.foo(_ closure:)` で渡されたクロージャを関数実行後に呼び出すため `storedClosure` プロパティに保管しています．
このような場合 `@escaping` を付ける必要があります．
もし `@escaping`を付けなかった場合は，以下のようなコンパイルエラーが発生します（ `@escaping` を付けなさいと言われる）．

```
MyPlayground.playground:27:14: note: parameter 'closure' is implicitly non-escaping
  func foo(_ closure: () -> Void) {
             ^
                      @escaping 
```

`URLSession.downloadTask(with:completionHandler:)` の定義（ `func downloadTask(with url: URL, completionHandler: @escaping (URL?, URLResponse?, Error?) -> Void) -> URLSessionDownloadTask` ）を見るとわかるように，クロージャが保管され，関数の実行完了後に非同期で呼ばれるような関数には `@escaping` が付いています．


## weak/unowned の理解

まず， `weak` / `unowned` の文法上の出現位置は，クロージャ定義のパラメータリストの前です．

```swift:
class Foo {
  let colsure = { [weak self] in
  }
}
```

### 循環参照が起こるパターン

次のようにメモリが開放されたら `deinit` でメッセージを出す `Foo` があるとします．

```swift:
class Foo {
  deinit {
    print("foo deinit")
  }
}

var foo: Foo! = Foo()
foo = nil

// --> foo deinit
```

しかし，クロージャがFooのインスタンス自身を強参照している（ `weak` / `unowned` を付けない）場合，クロージャのインスタンスとFooのインスタンスが相互に参照し合うのでメモリが開放されません．

```swift:
class Foo {
  var storedClosure: (() -> Void)?
  let name = "foo"

  func foo() {
    storedClosure = {
        print(self.name)
    }
  }

  deinit {
    print("foo deinit")
  }
}

var foo: Foo! = Foo()
foo.foo()
foo.storedClosure!()
foo = nil

// --> foo
```

参照の関係性は次のようになります．

![cyclic_reference.png](https://qiita-image-store.s3.amazonaws.com/0/35592/60df7695-7e42-cc4c-77ae-7c2106b23ddf.png "cyclic_reference.png")

### 循環参照を切るための weak/unowned

この循環参照を断ち切るために `weak` / `unowned` を使います．

```swift:
class Foo {
  var storedClosure: (() -> Void)?
  let name = "foo"

  func foo() {
    storedClosure = { [weak self] in
      if let weakSelf = self {
        print(weakSelf.name)
      }
    }
  }

  deinit {
    print("foo deinit")
  }
}

var foo: Foo! = Foo()
foo.foo()
foo.storedClosure!()
foo = nil

// --> foo
// --> foo deinit
```

クロージャの引数の前に `[weak self]` を宣言することで，クロージャからself( `foo` )に対する参照を弱参照にし， `foo` のメモリ解放が行われました．この時の参照関係は次のようになります．

![weak_reference.png](https://qiita-image-store.s3.amazonaws.com/0/35592/189e84bf-7518-6158-27f6-dc4c27343e71.png "weak_reference.png")


### weakとunowned の違い

* `weak` は弱参照先がメモリ解放されている場合nilになります（つまり通常のOptionalと同様にアンラップが必要です．）
* `unowned` はOptionalではないので，弱参照先がメモリ開放されていた場合クラッシュします

よって，参照先（先の例では `foo` ）を参照する際にnilでないことが保証されるなら `unowned` を使います．どちらにすべきか迷ったら `weak` を使っておくとよいと思います．

循環参照を断ち切る例を `unowned` で書くと次のようになります（クロージャ内でアンラップせずに `self` を使えています.）

```swift:
class Foo {
  var storedClosure: (() -> Void)?
  let name = "foo"

  func foo() {
    storedClosure = { [unowned self] in
      print(self.name)
    }
  }

  deinit {
    print("foo deinit")
  }
}

var foo: Foo! = Foo()
foo.foo()
foo.storedClosure!()
foo = nil

// --> foo
// --> foo deinit
```

`unowned` を使っているのに参照時にnilである場合の例は，次のようになります．

```swift:
class Foo {
  var storedClosure: (() -> Void)?
  let name = "foo"

  func foo() {
    storedClosure = { [unowned self] in
      print(self.name)
    }
  }

  deinit {
    print("foo deinit")
  }
}

var foo: Foo! = Foo()
foo.foo()
let fooStoredClosure = foo.storedClosure
foo = nil
fooStoredClosure!() // fooがnilになった後でクロージャを呼ぶ

// --> foo deinit
```

"foo deinit" の出力の後に `fooStoredClosure!()` で次のような実行時エラーが発生しました．

![unowned_error.png](https://qiita-image-store.s3.amazonaws.com/0/35592/bc78c523-a435-57e6-2808-85ed9ac7721a.png "unowned_error.png")


### weak/unowned の使い所

具体的な使い所としては

* `delegate` の宣言（一般的にデリゲートの参照先とデリゲートを持つオブジェクトは循環参照する）
* `UIViewController` における `completion` ブロック内での `self` の参照（ `completion` を持つ関数のオブジェクトを `UIViewController` がプロパティで保持しているケースがある）

などが多いと思います．

#### delegate の例

```swift
protocol FooDelegate: class {
  func delegateFunc()
}

class Foo {
  weak var delegate: FooDelegate?

  func callDelegate() {
    delegate?.delegateFunc()
  }

  deinit {
    print("foo deinit")
  }
}

class Bar: FooDelegate {
  var foo: Foo?

  func bar() {
    foo = Foo()
    foo?.delegate = self
    foo?.callDelegate()
  }

  func delegateFunc() {
    print("bar delegate func")
  }

  deinit {
    print("bar deinit")
  }
}

var bar: Bar! = Bar()
bar.bar()
bar = nil

// --> bar delegate func
// --> bar deinit
// --> foo deinit
```

参照の関係は次のようになります．

![delegate_example-2.png](https://qiita-image-store.s3.amazonaws.com/0/35592/46db9aac-d861-552d-cb5d-1a9c4dc459d2.png "delegate_example-2.png")

もし `delegate` に `weak` を付けなかった場合 "bar deinit" と "foo deinit" が出力されません．

#### UIViewController の例

`ViewController` とクロージャと `UIAlertController` のオブジェクトが3者で循環参照になってしまうため，クロージャから `ViewController` の参照の部分で `unowned` を使い弱参照にして循環を断ち切っています．

```swift:
class ViewController: UIViewController {

  @IBOutlet weak var label: UILabel!

  var dlg: UIAlertController!

  override func viewDidLoad() {
    super.viewDidLoad()
    dlg = UIAlertController(title: "", message: "", preferredStyle: .alert)
    dlg.addAction(UIAlertAction(title: "ok", style: .default) { [unowned self] _ in
      self.label.text = "ok selected"
    })
  }

  override func viewDidAppear(_ animated: Bool) {
    present(dlg, animated: true)
  }

}
```

参照の関係は次のようになります．

![uiviewcontroller_example.png](https://qiita-image-store.s3.amazonaws.com/0/35592/bf7cdf0f-02d9-fbd7-520e-aca31f3df23a.png "uiviewcontroller_example.png")


## escaping と weak/unowned の関係

`@escaping` されたクロージャは先の `UIViewController` の例のように，循環参照を発生させる可能性があります．その場合に，クロージャ内で参照する変数を `weak` / `unowned` により弱参照にすることで，循環参照を断ち切りメモリ解放漏れを防ぎます．