Escaping Closures in Swift
I find this website very helpful on that matter Simple explanation would be:
If a closure is passed as an argument to a function and it is invoked after the function returns, the closure is escaping.
Read more at the link I passed above! :)
Consider this class:
class A {
var closure: (() -> Void)?
func someMethod(closure: @escaping () -> Void) {
self.closure = closure
}
}
someMethod
assigns the closure passed in, to a property in the class.
Now here comes another class:
class B {
var number = 0
var a: A = A()
func anotherMethod() {
a.someMethod { self.number = 10 }
}
}
If I call anotherMethod
, the closure { self.number = 10 }
will be stored in the instance of A
. Since self
is captured in the closure, the instance of A
will also hold a strong reference to it.
That's basically an example of an escaped closure!
You are probably wondering, "what? So where did the closure escaped from, and to?"
The closure escapes from the scope of the method, to the scope of the class. And it can be called later, even on another thread! This could cause problems if not handled properly.
By default, Swift doesn't allow closures to escape. You have to add @escaping
to the closure type to tell the compiler "Please allow this closure to escape". If we remove @escaping
:
class A {
var closure: (() -> Void)?
func someMethod(closure: () -> Void) {
}
}
and try to write self.closure = closure
, it doesn't compile!
I am going in a more simpler way.
Consider this example:
func testFunctionWithNonescapingClosure(closure:() -> Void) {
closure()
}
The above is a non-escaping closure because the closure is invoked before the method returns.
Consider the same example with an asynchoronous operation:
func testFunctionWithEscapingClosure(closure:@escaping () -> Void) {
DispatchQueue.main.async {
closure()
}
}
The above example contains an escaping closure because the closure invocation may happen after the function returns due to the asynchronous operation.
var completionHandlers: [() -> Void] = []
func testFunctionWithEscapingClosure(closure: @escaping () -> Void) {
completionHandlers.append(closure)
}
In the above case you can easily realize the closure is moving outside body of the function so it needs to be an escaping closure.
Escaping and non escaping closure were added for compiler optimization in Swift 3. You can search for the advantages of nonescaping
closure.