web-dev-qa-db-ja.com

Swift 3:エスケープしないパラメーターを使用すると、エスケープできる可能性があります

完了ハンドラーがある次の関数がありますが、このエラーが発生します。

Closure use of non-escaping parameter may allow it to escape

これが私のコードです:

func makeRequestcompletion(completion:(_ response:Data, _ error:NSError)->Void)  {
    let urlString = URL(string: "http://someUrl.com")
    if let url = urlString {
        let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, urlRequestResponse, error) in
            completion(data, error) // <-- here is I'm getting the error
        })
    task.resume()
    }
}

enter image description here なぜ私はこのエラーが出るのか知っていますか?

あなたの助けに本当に感謝します

14
user2924482

クロージャがエスケープできることを明示的に定義する必要があるようです。

Apple Developer docs から、

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

TLDR; @escaping補完変数の後のキーワード:

func makeRequestcompletion(completion: @escaping (_ response:Data, _ error:NSError)->Void)  {
    let urlString = URL(string: "http://someUrl.com")
    if let url = urlString {
        let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, urlRequestResponse, error) in
            completion(data, error) // <-- here is I'm getting the error
        })
        task.resume()
    }
}
8
Mark Barrasso

「エスケープ」クロージャーは、それが作成されたスコープを存続させることができるクロージャーです。エスケープクロージャーは、参照カウントとメモリー管理に関して特別な注意を必要とし、最適化が難しくなる可能性があります。

Swift 3以前は、クロージャーのデフォルトはエスケープしていると想定していました。これは、開発者が既知のクロージャーを具体的に特定する必要があることを意味しましたnotエスケープして許可する最適化を行うコンパイラー。コミュニティーは、実際には、クロージャーがエスケープするかどうかをコンパイラー自体が簡単に検出できることを発見し、エスケープへの積極的なアプローチがより高速なコードにつながる可能性があることを決定しました。エスケープしないと見なされ、@escaping属性でエスケープしているクロージャーにフラグを立てる必要があります。

あなたの場合、URLSession.shared.dataTaskが受け入れるクロージャ自体がエスケープクロージャであるため、その内部でクロージャを使用する場合は、@escapingもマークする必要があります。

3
zneak

@escapingはすべての呼び出しメソッドに対して感染性があり、コンパイラーは、いつmustを含める必要があるかを決定します。

次の例(コンパイル)を考えてみます。

dispatchSometime( { print("Oh yeah") })

func dispatchSometime(_ block: ()->()) {
    dispatchNow(block)
}

func dispatchNow(_ block: ()->()) {
    block()
}

ただし、この変更例では、タイプnon-escaping parameter may allow it to escapeの-​​twoエラーが生成されます。

dispatchSometime( { print("Oh yeah") })

func dispatchSometime(_ block: ()->()) {
    dispatchLater(block)
}

func dispatchLater(_ block: ()->()) {
    DispatchQueue.main.async(execute: block)
}

MainでのディスパッチはdispatchLaterメソッドが@escapingを必要とすることを意味し、それを追加したらdispatchSometimeメソッドalso@escapingを必要としますサンプルをコンパイルします。

dispatchSometime( { print("Oh yeah") })

func dispatchSometime(_ block: @escaping ()->()) {
    dispatchLater(block)
}

func dispatchLater(_ block: @escaping ()->()) {
    DispatchQueue.main.async(execute: block)
}

ただし、要点は次のとおりです。

  • コンパイラが文句を言うまで@escapingを呼び出しチェーンに追加し続けます。
  • キーワードは何も変更しません。これは、本質的に、「ブロック自体と一緒に保持される可能性があるので、キャプチャされた変数でweakを使用するように注意してください」という警告です。

意味

これが本当に楽しいのは、いくつかのメソッドを調整して@escapingキーワードを含める必要がある場合です。これにより、コンパイラーは文句を言わなくなります。ただし、これらのメソッドが実際にプロトコルに準拠している場合、そのプロトコルのメソッドはalso@escapingキーワードを取得する必要があり、これは他のすべてのプロトコル準拠にも感染します。楽しい!

1
Dan Rosenstark