user2924482
user2924482

Reputation: 9120

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

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

Upvotes: 16

Views: 15791

Answers (3)

Dan Rosenstark
Dan Rosenstark

Reputation: 69757

@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!

Upvotes: 3

zneak
zneak

Reputation: 138051

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.

Upvotes: 4

Mark Barrasso
Mark Barrasso

Reputation: 649

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()
    }
}

Upvotes: 16

Related Questions