Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

People also ask

What is escaping and non-escaping closures?

Escaping closures are different from non-escaping closures since we can preserve escaping closures to execute them later on. Meanwhile, the function body can execute and returns the compiler back. The scope of the escaping closure exists and has existence in memory as well until it gets executed.

What are escaping closures?

An escaping closure is a closure that's called after the function it was passed to returns. In other words, it outlives the function it was passed to. A non-escaping closure is a closure that's called within the function it was passed into, i.e. before it returns.

What does @escaping do Swift?

In Swift, a closure marked with @escaping means the closure can outlive the scope of the block of code it is passed into. In a sense, @escaping tells the closure to “stay up even after the surrounding function is gone”.


This is due to a change in the default behaviour for parameters of function type. Prior to Swift 3 (specifically the build that ships with Xcode 8 beta 6), they would default to being escaping – you would have to mark them @noescape in order to prevent them from being stored or captured, which guarantees they won't outlive the duration of the function call.

However, now @noescape is the default for function-typed parameters. If you want to store or capture such functions, you now need to mark them @escaping:

protocol DataServiceType {
  func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void)
  func cachedData(location: String) -> Data?
}

func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void) {
  // ...
}

See the Swift Evolution proposal for more info about this change.


Since @noescape is the default, there 2 options to fix the error:

1) as @Hamish pointed out in his answer, just mark the completion as @escaping if you do care about the result and really want it to escape (that probably is the case in @Lukasz's question with Unit Tests as example and possibility of async completion)

func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void)

OR

2) keep the default @noescape behavior by making the completion optional discarding the results altogether in cases when you don't care about the result. For example when user has already "walked away" and the calling view controller doesn't have to hang in memory just because there was some careless network call. Just as it was in my case when I came here looking for answer and the sample code wasn't very relevant for me, so marking @noescape was not the best option, event though it sounded as the only one from the first glance.

func fetchData(location: String, completion: ((DataFetchResult) -> Void)?) {
   ...
   completion?(self.result)
}