こんにちは。きんくまです。
自分でクロージャーを使ったメソッドを作りたかったときに、@escapingのことをよくわからなかったので調べたメモです。
参考サイト
>> The Swift Programming Language (Swift 4): Closures
>> What Do @escaping and @noescape Mean In Swift 3
@escapingしない場合
まず、@escapingしないクロージャーの場合を作ってみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | class MathUtil{ var num1: Int var num2: Int init (num1: Int , num2: Int ){ self .num1 = num1 self .num2 = num2 } //クロージャーの返り値を10倍する func tenTimes(equation:(( Int , Int )-> Int )) -> Int { return equation(num1, num2) * 10 } } class ViewController: UIViewController { var num3: Int = 2 var util:MathUtil? = MathUtil(num1: 5 , num2: 3 ) override func viewDidLoad() { super .viewDidLoad() test_tenTimes1() test_tenTimes2() test_tenTimes3() } func test_tenTimes1(){ guard let util = self .util else { return } let result = util.tenTimes { (num1: Int , num2: Int ) -> Int in return num1 + num2 } print( "test_tenTimes1 result = \(result)" ) //result = 80 } func test_tenTimes2(){ guard let util = self .util else { return } let result = util.tenTimes { (num1: Int , num2: Int ) -> Int in return num1 - num2 } print( "test_tenTimes2 result = \(result)" ) //result = 20 } func test_tenTimes3(){ guard let util = self .util else { return } //selfをつけていないことに注目 let result = util.tenTimes { (num1: Int , num2: Int ) -> Int in return num1 + num2 + num3 } print( "test_tenTimes3 result = \(result)" ) //result = 100 } } |
MathUtilというクラスの中で、クロージャーを引数にとるメソッドを作りました。
tenTimes()は、引数のクロージャーの返り値を10倍するメソッドです。
MathUtilを使うのは、実際の場面でありそうなViewControllerが使うことにしました。
@escapingをつけるときと、つけないときの場合分け
@escapingをつけなくてもいいのは以下の場合です。
A-1. クロージャーがプロパティとして保存されない(強参照されない)
かつ
A-2. クロージャーがメソッド内ですぐに実行される(非同期でない)
逆に、@escapingをつけるのはその逆で以下になります。
B-1. クロージャーがプロパティとして保存される(強参照される)
または
B-2. クロージャーがメソッド内ですぐに実行されない(非同期である)
B-1かB-2のどちらかの場合は@escapingする必要があります。
上のコード例では、どちらの場合でもないため、@escapingする必要がありませんでした。
B-1. クロージャーがプロパティとして保存される(強参照される)場合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | class MathUtil{ ..同じところは省略 var storedEquation:(( Int , Int )-> Int )? //クロージャーを内部で保持する func tenTimesWithStore(equation: @escaping (( Int , Int )-> Int )) -> Int { self .storedEquation = equation //ここで保持 return equation(num1, num2) * 10 } } class ViewController: UIViewController { ..同じところは省略 override func viewDidLoad() { super .viewDidLoad() test_tenTimesWithStore() test_tenTimesWithStore2() } func test_tenTimesWithStore(){ guard let util = self .util else { return } let result = util.tenTimesWithStore { (num1: Int , num2: Int ) -> Int in return num1 + num2 } print( "test_tenTimesWithStore result = \(result)" ) //result = 80 } func test_tenTimesWithStore2(){ guard let util = self .util else { return } //内部でselfを使う場合は、weakかunownedで参照する let result = util.tenTimesWithStore { [ unowned self ](num1: Int , num2: Int ) -> Int in self .num3 = 1 return num1 + num2 + self .num3 } print( "test_tenTimesWithStore2 result = \(result)" ) //result = 90 } } |
クロージャーがメソッド内などからプロパティとして保持される場合は、@escapingする必要があります。(今回はMathUtil.tenTimesWithStore)
また、クロージャー内で呼び出し元のプロパティやメソッドを呼びたい場合は、weakかunownedをつけた方がよいです。
つけないと循環参照になってしまい、メモリーリークしてしまいます。
weakとunownedの使い分けなのですが、こちらが参考になりました。(これを調べるまで、私がいつもunownedしか使ってなかったというのは秘密ですw)
今回ViewControllerは破棄されない場面を想定して、test_tenTimesWithStore2の中ではunownedを使っています。
>> Swiftのクロージャにおける循環参照問題でunownedとweakの使い分けがわからない
また、気がついた人がいるかもしれませんが、@escapingしない場合は、クロージャー内でselfをつける必要がないみたいです。(Appleの公式に書いてあった。今回でいうと一番はじめのコードです)
B-2. クロージャーがメソッド内ですぐに実行されない(非同期である)場合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | class MathUtil{ //非同期後に実行する場合(今回はタイマー) func tenTimesWithAsync(callback: @escaping (( Int )->Void)) -> Void { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0 ) { [ weak self ] in //ここはtenTimesWithAsyncメソッドが終わったあとに非同期で実行される guard let myself = self else { return } callback((myself.num1 + myself.num2) * 10 ) } } } class ViewController: UIViewController { ..同じところは省略 override func viewDidLoad() { super .viewDidLoad() test_tenTimesWithAsync() } func test_tenTimesWithAsync(){ guard let util = self .util else { return } //内部でselfを使う場合は、weakかunownedで参照する util.tenTimesWithAsync { [ unowned self ](result: Int ) in print( "test_tenTimesWithAsync result \(result)" ) //result = 80 self .num3 = 4 let result2 = self .num3 + result print( "test_tenTimesWithAsync result2 \(result2)" ) //result = 84 self .util = nil } } } |
この非同期実行のときの方が、実際の場面では多いのではないでしょうか。
今回は1秒後にクロージャーが実行されて、計算結果が返ってくるというサンプルにしてみました。
補足 swift2までの@escaping
swift3から@noescapeがデフォルトの挙動になったみたいです。だから、escapeするときは、@escapingの記述が必要になったということ。
swift2までは逆で、escapeがデフォルトだったので、noescapeするときだけ@noescapeを記述する必要があったみたい。
コード全部
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | import UIKit class MathUtil{ var num1: Int var num2: Int var storedEquation:(( Int , Int )-> Int )? init (num1: Int , num2: Int ){ self .num1 = num1 self .num2 = num2 } deinit { print( "deinit MathUtil" ) } //クロージャーの返り値を10倍する func tenTimes(equation:(( Int , Int )-> Int )) -> Int { return equation(num1, num2) * 10 } //クロージャーを内部で保持する func tenTimesWithStore(equation: @escaping (( Int , Int )-> Int )) -> Int { self .storedEquation = equation return equation(num1, num2) * 10 } //非同期後に実行する場合 func tenTimesWithAsync(callback: @escaping (( Int )->Void)) -> Void { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0 ) { [ weak self ] in guard let myself = self else { return } callback((myself.num1 + myself.num2) * 10 ) } } } class ViewController: UIViewController { var num3: Int = 2 var util:MathUtil? = MathUtil(num1: 5 , num2: 3 ) override func viewDidLoad() { super .viewDidLoad() test_tenTimes1() test_tenTimes2() test_tenTimes3() test_tenTimesWithStore() test_tenTimesWithStore2() test_tenTimesWithAsync() } func test_tenTimes1(){ guard let util = self .util else { return } let result = util.tenTimes { (num1: Int , num2: Int ) -> Int in return num1 + num2 } print( "test_tenTimes1 result = \(result)" ) //result = 80 } func test_tenTimes2(){ guard let util = self .util else { return } let result = util.tenTimes { (num1: Int , num2: Int ) -> Int in return num1 - num2 } print( "test_tenTimes2 result = \(result)" ) //result = 20 } func test_tenTimes3(){ guard let util = self .util else { return } //selfをつけていないことに注目 let result = util.tenTimes { (num1: Int , num2: Int ) -> Int in return num1 + num2 + num3 } print( "test_tenTimes3 result = \(result)" ) //result = 100 } func test_tenTimesWithStore(){ guard let util = self .util else { return } let result = util.tenTimesWithStore { (num1: Int , num2: Int ) -> Int in return num1 + num2 } print( "test_tenTimesWithStore result = \(result)" ) //result = 80 } func test_tenTimesWithStore2(){ guard let util = self .util else { return } //内部でselfを使う場合は、weakかunownedで参照する let result = util.tenTimesWithStore { [ unowned self ](num1: Int , num2: Int ) -> Int in self .num3 = 1 return num1 + num2 + self .num3 } print( "test_tenTimesWithStore2 result = \(result)" ) //result = 90 } func test_tenTimesWithAsync(){ guard let util = self .util else { return } //内部でselfを使う場合は、weakかunownedで参照する util.tenTimesWithAsync { [ unowned self ](result: Int ) in print( "test_tenTimesWithAsync result \(result)" ) //result = 80 self .num3 = 4 let result2 = self .num3 + result print( "test_tenTimesWithAsync result2 \(result2)" ) //result = 84 self .util = nil } } } |
出力
1 2 3 4 5 6 7 8 | test_tenTimes1 result = 80 test_tenTimes2 result = 20 test_tenTimes3 result = 100 test_tenTimesWithStore result = 80 test_tenTimesWithStore2 result = 90 test_tenTimesWithAsync result 80 test_tenTimesWithAsync result2 84 deinit MathUtil |
■ 自作iPhoneアプリ 好評発売中!
・フォルメモ - シンプルなフォルダつきメモ帳
・ジッピー電卓 - 消費税や割引もサクサク計算!
■ LINEスタンプ作りました!
毎日使える。とぼけたウサギ