Escaping Closures in Swift
Asked Answered
W

9

56

I'm new to Swift and I was reading the manual when I came across escaping closures. I didn't get the manual's description at all. Could someone please explain to me what escaping closures are in Swift in simple terms.

Walburga answered 15/9, 2016 at 6:13 Comment(2)
To quote from the manual, “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.” So, if the closure is called synchronously, it's non-escaping. An example might be an enumeration closure, or the map, filter, etc. functional methods. If it's called asynchronously (i.e. later), it's escaping. The most common example of escaping closure would be the completion handler for some slow asynchronous task, like a network request.Nero
If you think my answer answers your question, please consider accepting by clicking on that checkmark.Crispi
C
82

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!

Crispi answered 15/9, 2016 at 6:27 Comment(7)
@bandejapaisa You say "@noescape" has been deprecated, but I have recently run into a situation that makes me think it is only semi-deprecated. Please take a look at this and let me know if I've made some mistake: stackoverflow.com/questions/41917413/…Carlettacarley
@noescape is the implicit behaviour. The keyword is deprecated, not completely removed yet, but the behaviour you get by writing it or not, is that your closure does NOT escape from the scope of the method. Deprecated means it's still available to use, but it will be removed in a future version. Hence why you can still use it, and why you think it's 'semi deprecated'Zoology
From your answer, I still didn't understand the difference - what is each type good for. It would be nicer if you provided a real-world example. The other two answers are much easier to understand.Philippopolis
@HonzaKalfus Sometimes closures escape and sometimes they don't. It all depends on what the method accepting the closure is doing. Escaping closures might cause retain cycles, non-escaping ones usually don't. @escaping is a sign that tells the caller "beware, this closure will escape. make sure to capture self weakly or unowned".Crispi
@Crispi In The Swift Programming Language book, the authors claim this: "Swift uses capture lists to break these strong reference cycles." I haven't read the dedicated chapter on this issue though. But I'm unsure if that's even related to what you're saying :) Anyway, that wasn't the point of my comment. The point of my comment was that you could perhaps think about updating your answer by using a real-world example. Also, Swift 3 version would be nice, rather than 2, which is no longer used ;)Philippopolis
@Nero How about now?Crispi
Perfect. Sorry to nag, but I wanted to link to this when marking another question as a duplicate, and didn’t want to do that with the outdated syntax for fear of confusing a newer Swift developer (and I didn’t want to presumptuously just edit your answer).Nero
P
53

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.

Pratique answered 2/3, 2017 at 8:35 Comment(1)
It would be good to also add to this good answer what advantages of the nonescaping closure there are and examples when you can need it.Magnusson
C
17

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! :)

Charlenecharleroi answered 8/12, 2016 at 15:47 Comment(0)
G
10

By default the closures are non escaping. For simple understanding you can consider non_escaping closures as local closure(just like local variables) and escaping as global closure (just like global variables). It means once we come out from the method body the scope of the non_escaping closure is lost. But in the case of escaping closure, the memory retained the closure int the memory.

***Simply we use escaping closure when we call the closure inside any async task in the method, or method returns before calling the closure.

Non_escaping closure: -

func add(num1: Int, num2: Int, completion: ((Int) -> (Void))) -> Int {
    DispatchQueue.global(qos: .background).async {
        print("Background")
        completion(num1 + num2) // Error: Closure use of non-escaping parameter 'completion' may allow it to escape
    }
    return num1
}

override func viewDidLoad() {
    super.viewDidLoad()
    let ans = add(num1: 12, num2: 22, completion: { (number) in
        print("Inside Closure")
        print(number)
    })
    print("Ans = \(ans)")
    initialSetup()
}

Since it is non_escaping closure its scope will be lost once we come out the from the 'add' method. completion(num1 + num2) will never call.

Escaping closure:-

func add(num1: Int, num2: Int, completion: @escaping((Int) -> (Void))) -> Int {
    DispatchQueue.global(qos: .background).async {
        print("Background")
        completion(num1 + num2)
    }
    return num1
}

Even if the method return (i.e., we come out of the method scope) the closure will be called.enter code here

Greatnephew answered 24/1, 2019 at 12:49 Comment(0)
L
6

Swift 4.1

From Language Reference: Attributes of The Swift Programming Language (Swift 4.1)

Apple explains the attribute escaping clearly.

Apply this attribute to a parameter’s type in a method or function declaration to indicate that the parameter’s value can be stored for later execution. This means that the value is allowed to outlive the lifetime of the call. Function type parameters with the escaping type attribute require explicit use of self. for properties or methods. For an example of how to use the escaping attribute, see Escaping Closures

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

The someFunctionWithEscapingClosure(_:) function takes a closure as its argument and adds it to an array that’s declared outside the function. If you didn’t mark the parameter of this function with @escaping, you would get a compile-time error.

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.

Limekiln answered 4/6, 2018 at 8:2 Comment(0)
A
3

non-escaping(@noescape) vs escaping(@escaping) closure

[Function and closure]

non-escaping closure

@noescape is a closure which is passed into a function and which is called before the function returns

A good example of non-escaping closure is Array sort function - sorted(by: (Element, Element) -> Bool). This closure is called during executing a sort calculations.

History: @noescape was introduced in Swift 2 -> was deprecated in Swift 3 where became a default that is why you should mark @escaping attribute explicitly.

func foo(_ nonEscapingClosure: () -> Void) {
    nonEscapingClosure()
}

escaping closure

escaping closure(reference) is alive when method was finished.

//If you have next error
Escaping closure captures non-escaping parameter
//`@escaping` to the rescue 

@escaping is a closure which is

  1. passed into a function
  2. The owner function saves this closure in a property
  3. closure is called(using property) after the owner function returns(async)

A good example of an escaping closure is a completion handler in asynchronous operation. If you do not mark you function with @escaping in this case you get compile time error. Reference to property in an escaping closure requires you to use self explicitly

class MyClass {
    var completionHandler: (() -> Void)?

    func foo(_ escapingClosure: @escaping () -> Void) {
        
        //if you don't mark as @escaping you get
        //Assigning non-escaping parameter 'escapingClosure' to an @escaping closure
        completionHandler = escapingClosure //<- error here
    }

    func onEvent() {
        completionHandler?()
    }
}

completion is a completionHandlerAbout, if you want to highlight that it is @escaping you can use completionHandler naming

func foo(completion: () -> Void) {
    //
}
func foo(completionHandler: @escaping () -> Void) {
    //
}

Optional closures is @escaping by default

Closure is already escaping in optional type argument

func foo(completion: @escaping (() -> Void)?) { //trigger this build error - Closure is already escaping in optional type argument
    //some logic 

Optional - actually is an enum which encapsulates an @escaping function internally

[Sync vs Async]
[Java functional interfaces]

Azotize answered 2/4, 2020 at 11:32 Comment(0)
T
3

Definition of Escape

Swift’s closures are reference types, which means if you point two variables at the same closure they share that closure – Swift just remembers that there are two things relying on it by incrementing its reference count.

When a closure gets passed into a function to be used, Swift needs to know whether that function will get used immediately or whether it will be saved for use later on. If it’s used immediately, the compiler can skip adding one to its reference count because the closure will be run straight away then forgotten about. But if it’s used later – or even might be used later – Swift needs to add one to its reference count so that it won’t accidentally be destroyed.

Quick Example

A good example of an escaping closure is a completion handler. It’s executed in the future, when a lengthy task completes, so it outlives the function it was created in. Another example is asynchronous programming: a closure that’s executed asynchronously always escapes its original context.

public func responseData(
    queue: DispatchQueue? = nil,
    completionHandler: @escaping (DataResponse<Data>) -> Void)
    -> Self
{
    ...

Extra Information

For performance reasons, Swift assumes all closures are nonescaping closures, which means they will be used immediately inside the function and not stored, which in turn means Swift doesn’t touch the reference count. If this isn’t the case – if you take any measure to store the closure – then Swift will force you to mark it as @escaping so that the reference count must be changed.

Teillo answered 20/12, 2020 at 14:11 Comment(0)
T
0

Here simple example of escaping and no escaping closures.
Non-Scaping Closures

class NonEscapingClosure {

func performAddition() {
    print("Process3")
    addition(10, 10) { result in
        print("Process4")
        print(result)
    }
    print("Process5")
}

func addition(_ a: Int, _ b: Int, completion: (_ result: Int) -> Void) {
    print("Process1")
    completion(a + b)
    print("Process2")
}}

Creating an instance and calling function calling

let instance = NonEscapingClosure()
    instance.performAddition()

Output:

 Process3
 Process1
 Process4
 20
 Process2
 Process5

And Escaping Closures

class EscapingClosure {

func performAddition() {
    print("Process4")
    addition(10, 10) { result in
        print(result)
    }
    print("Process5")
}

func addition(_ a: Int, _ b: Int, completion: @escaping (_ result: Int) -> Void) {
    print("Process1")
    let add = a + b
    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        print("Process2")
        completion(add)
    }
    print("Process3")
}}

Creating an instance and function calling

let instance = EscapingClosure()
    instance.performAddition()

Output

 Process4
 Process1
 Process3
 Process5
 Process2
 20
Theodore answered 1/11, 2022 at 11:33 Comment(0)
N
0

An escaping closure is one that we want to run at a later point in time. By default all closures we define in Swift are executed immediately. Meaning as soon as they are encountered at runtime, then are executed and done.

Escaping closures are ones that get stored and are executed at some point in the future - like when a network call completes.

So think of regular closures as closures that get executed immediately. And escaping closures as ones that get stored and are executed at some future point in time.

Nahtanha answered 10/9, 2023 at 12:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.