Swiftでクロージャを使うコードを書いていると,よく @escaping とか weak / unowned といったワードを目にすることがあります.
なんとなく「コンパイラに怒られたら付ける」とか「参考にしたコードに書いてあったから付ける」などとしてしまっていたのですが(反省),ちゃんと意味を調べてみてもなかなか理解ができませんでした.
そこで,参考にした記事と私が動作確認したコードを示し,「こう考えたら私は理解できた」というメモを残しておきます.
なお,概念の理解を優先しているため,用語や表現の正確性は無いかもしれません.
参考にした記事
- Swift 3 の @escaping とは何か - Qiita
 - "Weak, Strong, Unowned, Oh My!" - A Guide to References in Swift — KrakenDev
 
環境
- Swift 3.1
 - Xcode 8.3.3
 
escaping の理解
まず, @escaping の文法上の出現位置は,関数定義のパラメータリスト中におけるクロージャの型定義の前です.
class Foo {
  func foo(_ closure: @escaping () -> Void) {
  }
}
escape=退避=非同期実行 と考えてみる
この @escaping が付いているということは,
- 渡すクロージャが退避(関数のスコープ外に保管(コピー))される
 - 渡すクロージャが関数( 
foo)の実行完了後に呼ばれる(関数実行後も生存し続ける) - 渡すクロージャが非同期実行される
 
と考えることができます.
escaping が必要ない場合
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 が必要な場合
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 の文法上の出現位置は,クロージャ定義のパラメータリストの前です.
class Foo {
  let colsure = { [weak self] in
  }
}
循環参照が起こるパターン
次のようにメモリが開放されたら deinit でメッセージを出す Foo があるとします.
class Foo {
  deinit {
    print("foo deinit")
  }
}
var foo: Foo! = Foo()
foo = nil
// --> foo deinit
しかし,クロージャがFooのインスタンス自身を強参照している( weak / unowned を付けない)場合,クロージャのインスタンスとFooのインスタンスが相互に参照し合うのでメモリが開放されません.
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
参照の関係性は次のようになります.
循環参照を切るための weak/unowned
この循環参照を断ち切るために weak / unowned を使います.
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とunowned の違い
- 
weakは弱参照先がメモリ解放されている場合nilになります(つまり通常のOptionalと同様にアンラップが必要です.) - 
unownedはOptionalではないので,弱参照先がメモリ開放されていた場合クラッシュします 
よって,参照先(先の例では foo )を参照する際にnilでないことが保証されるなら unowned を使います.どちらにすべきか迷ったら weak を使っておくとよいと思います.
循環参照を断ち切る例を unowned で書くと次のようになります(クロージャ内でアンラップせずに self を使えています.)
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である場合の例は,次のようになります.
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!() で次のような実行時エラーが発生しました.
weak/unowned の使い所
具体的な使い所としては
- 
delegateの宣言(一般的にデリゲートの参照先とデリゲートを持つオブジェクトは循環参照する) - 
UIViewControllerにおけるcompletionブロック内でのselfの参照(completionを持つ関数のオブジェクトをUIViewControllerがプロパティで保持しているケースがある) 
などが多いと思います.
delegate の例
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 に weak を付けなかった場合 "bar deinit" と "foo deinit" が出力されません.
UIViewController の例
ViewController とクロージャと UIAlertController のオブジェクトが3者で循環参照になってしまうため,クロージャから ViewController の参照の部分で unowned を使い弱参照にして循環を断ち切っています.
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)
  }
}
参照の関係は次のようになります.
escaping と weak/unowned の関係
@escaping されたクロージャは先の UIViewController の例のように,循環参照を発生させる可能性があります.その場合に,クロージャ内で参照する変数を weak / unowned により弱参照にすることで,循環参照を断ち切りメモリ解放漏れを防ぎます.




