Swift 3 :Closure use of non-escaping parameter may allow it to escape
Asked Answered
T

3

16

I have the following function where I have completion handler but I'm getting this error:

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

Here is my code:

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 Any of you knows why I'm getting this error?

I'll really appreciate you help

Tangerine answered 13/2, 2017 at 22:38 Comment(1)
Refer to this Closure use of non-escaping parameter may allow it to escapeUniformity
I
16

Looks like you need to explicitly define that the closure is allowed to escape.

From the Apple Developer docs,

A closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns. When you declare a function that takes a closure as one of its parameters, you can write @escaping before the parameter’s type to indicate that the closure is allowed to escape.

TLDR; Add the @escaping keyword after the completion variable:

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()
    }
}
Illogical answered 13/2, 2017 at 22:41 Comment(4)
Why is asking for the @escaping?Tangerine
Now in Swift 3 you must explicitly define when a function contains a completion handler that executes after the function has been called (escaping). Apple says, "The function returns after it starts the operation, but the closure isn’t called until the operation is completed—the closure needs to escape, to be called later. ... If you didn’t mark the parameter of this function with @escaping, you would get a compile-time error."Illogical
Just dropping by to thank @MarkBarrasso for his explanation. Been reading but couln't understand the whole "escaping" thing, and this comment made me see the light ;)Sawdust
Thanks @MarkBarrasso, your answer really helped me.Nihon
I
4

An "escaping" closure is a closure that can outlive the scope that it was created in. Escaping closures require special care around reference counting and memory management and can be harder to optimize.

Prior to Swift 3, the default for closures was to assume that they were escaping. This meant that developers had to specifically identify closures that are known not to escape to allow the compiler to make optimizations. The community found that in fact, the compiler could easily find out by itself if a closure is escaping or not, and decided that an aggressive approach to escaping could result in faster code. The result is that closures are now assumed to be non-escaping, and you need to flag closures that are escaping with the @escaping attribute.

In your case, the closure that URLSession.shared.dataTask accepts is itself an escaping closure, so if you use a closure inside of it, it also needs to be marked @escaping.

Inconsiderate answered 13/2, 2017 at 22:49 Comment(4)
@Linasses completion: @escaping (_ response:Data, _ error:NSError)->Void.Inconsiderate
@escaping error pops-up although I already have @escaping :| open static func resumeSession(_ completion: @escaping (Result<Session, Error>) -> (Void)) {@ escaping error pops-up although I already have @ escaping :| open static func resumeSession(_ completion: @ escaping (Result<Session, Error>) -> (Void)) {Reeba
Given that the compiler can detect when @escaping is required to be added (i.e. it generates an error) couldn't this simply have been left as transparent to the developer, and the compiler could still optimize under the hood? What's the advantage to making it explicit in this way?Aberrant
@StephenT, @escaping is part of the function's binary interface, just like the type of the closure itself. The compiler will generate better code when passing a closure that doesn't escape, but that code would cause unpredictable results (likely crashing) if the closure did, in fact, escape. If you distributed a binary framework and didn't annotate closures, an update could unwittingly break calling code. At least, with the explicit annotation, it forces you to consider what you're doing when you're changing escaping behavior and it can't happen by chance or surprise.Inconsiderate
T
3

@escaping is infectious to all calling methods, and the compiler determines when you must include it.

Consider this example (which compiles):

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

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

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

This modified example, however, produces two errors of type non-escaping parameter may allow it to escape:

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

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

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

The dispatch on main means the dispatchLater method needs @escaping, and once you've added that, the dispatchSometime method also requires @escaping for the example to compile.

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

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

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

However, the take away is just:

  • Keep adding @escaping up the call chain until the compiler stops complaining.
  • The keyword doesn't change anything: it's a warning which says, essentially, "be careful to use weak with captured variables as they may be retained along with the block itself."

Implications

The really fun case with this is where you have to adjust several methods to include the @escaping keyword, which gets the compiler to stop complaining. However, if those methods are actually conforming to a protocol, that protocol's methods must also get the @escaping keyword, which also infects all other protocol conformants. Fun!

Tonita answered 14/8, 2017 at 18:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.