web-dev-qa-db-ja.com

Swiftでのクロージャのエスケープ

Swiftが初めてで、クロージャのエスケープに遭遇したときにマニュアルを読んでいました。マニュアルの説明がまったくありませんでした。 Swift。

39
Nikhil Sridhar

このクラスを検討してください:

class A {
    var closure: (() -> Void)?
    func someMethod(closure: () -> Void) {
        self.closure = closure
    }
}

someMethodは、渡されたクロージャーをクラスのプロパティに割り当てます。

次は別のクラスです。

class B {
    var number = 0
    var a: A = A()
    func anotherMethod() {
        a.someMethod { self.number = 10 }
    }
}

anotherMethodを呼び出すと、クロージャ{ self.number = 10 }Aのインスタンスに保存されます。 selfはクロージャでキャプチャされるため、Aのインスタンスもそれへの強い参照を保持します。

これは基本的にエスケープされたクロージャーの例です!

あなたはおそらく、「何?閉鎖はどこから、そしてどこへ逃げたのか?」

クロージャーは、メソッドのスコープからクラスのスコープにエスケープします。また、別のスレッドでも後で呼び出すことができます!これは、適切に処理されないと問題を引き起こす可能性があります。

誤ってクロージャーをエスケープして保持サイクルやその他の問題を引き起こすのを防ぐには、@noescape属性:

class A {
    var closure: (() -> Void)?
    func someMethod(@noescape closure: () -> Void) {
    }
}

self.closure = closure、コンパイルされません!

更新:

Swift 3では、デフォルトではすべてのクロージャーパラメーターをエスケープできません。@escaping属性は、クロージャーを現在のスコープからエスケープできるようにします。これにより、コードの安全性が大幅に向上します!

class A {
    var closure: (() -> Void)?
    func someMethod(closure: @escaping () -> Void) {
    }
}
65
Sweeper

もっと簡単な方法で行っています。

この例を考えてみましょう:

func testFunctionWithNonescapingClosure(closure:() -> Void) {
        closure()
}

メソッドが戻る前にクロージャーが呼び出されるため、上記は非エスケープクロージャーです。

非同期操作を使用した同じ例を考えてみましょう。

func testFunctionWithEscapingClosure(closure:@escaping () -> Void) {
      DispatchQueue.main.async {
           closure()
      }
 }

非同期操作のために関数が戻った後にクロージャーの呼び出しが発生する可能性があるため、上記の例にはエスケープクロージャーが含まれています。

 var completionHandlers: [() -> Void] = []
 func testFunctionWithEscapingClosure(closure: @escaping () -> Void) {
      completionHandlers.append(closure)
 }

上記の場合、クロージャが関数の本体の外側に移動していることを簡単に理解できるので、エスケープクロージャにする必要があります。

エスケープと非エスケープクロージャーは、コンパイラ最適化のためにSwift 3.に追加されました。nonescapingクロージャーの利点を検索できます。

41
LC 웃

この問題についてこのウェブサイトは非常に役立つと思います 簡単な説明は次のとおりです:

クロージャが引数として関数に渡され、関数が戻った後に呼び出される場合、クロージャはエスケープされています。

上記で渡したリンクで詳細を読んでください! :)

14
stan

Swift 4.1

言語リファレンスから: 属性Swiftプログラミング言語(Swift 4.1)

Appleは属性escapingを明確に説明しています。

この属性をメソッドまたは関数宣言のパラメーターの型に適用して、パラメーターの値を後で実行するために保存できることを示します。これは、値が呼び出しの存続期間を超えて存続できることを意味します。エスケープ型属性を持つ関数型パラメーターには、selfの明示的な使用が必要です。プロパティまたはメソッド用。エスケープ属性の使用方法の例については、 Escaping Closures をご覧ください。

_var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}
_

someFunctionWithEscapingClosure(_:)関数は、引数としてクロージャーを受け取り、それを関数の外部で宣言された配列に追加します。この関数のパラメーターを_@escaping_でマークしなかった場合、コンパイル時エラーが発生します。

クロージャーは、関数に引数として渡されたときに関数をエスケープすると言われていますが、関数が戻った後に呼び出されます。パラメーターの1つとしてクロージャーを受け取る関数を宣言する場合、パラメーターのタイプの前に@escapingを記述して、クロージャーがエスケープできることを示すことができます。

3
black_pearl

デフォルトでは、クロージャーは非エスケープです。簡単に理解するために、non_escapingクロージャーをローカルクロージャー(ローカル変数のように)、エスケープをグローバルクロージャー(グローバル変数のように)として考えることができます。これは、メソッド本体から出るとnon_escapingクロージャーのスコープが失われることを意味します。ただし、クロージャをエスケープする場合、メモリはクロージャをメモリ内に保持していました。

***メソッドの非同期タスク内でクロージャーを呼び出すとき、またはクロージャーを呼び出す前にメソッドが戻るときに、エスケープクロージャーを使用します。

非エスケープクロージャ:-

func add(num1: Int, num2: Int, completion: ((Int) -> (Void))) -> Int {
    DispatchQueue.global(qos: .background).async {
        print("Background")
        completion(num1 + num2) // Error: Closure use of non-escaping parameter 'completion' may allow it to escape
    }
    return num1
}

override func viewDidLoad() {
    super.viewDidLoad()
    let ans = add(num1: 12, num2: 22, completion: { (number) in
        print("Inside Closure")
        print(number)
    })
    print("Ans = \(ans)")
    initialSetup()
}

Non_escapingクロージャーなので、 'add'メソッドから出てくるとそのスコープは失われます。 completion(num1 + num2)は呼び出されません。

エスケープクロージャ:-

func add(num1: Int, num2: Int, completion: @escaping((Int) -> (Void))) -> Int {
    DispatchQueue.global(qos: .background).async {
        print("Background")
        completion(num1 + num2)
    }
    return num1
}

メソッドが返された場合(つまり、メソッドスコープから出た場合)、クロージャーが呼び出されます。enter code here

1
DEEPAK KUMAR