Escaping Closures in Swift

2019-01-07 09:22发布

问题:

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.

回答1:

Consider this class:

class A {
    var closure: (() -> Void)?
    func someMethod(closure: () -> 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.

To avoid accidentally escaping closures and causing retain cycles and other problems, use the @noescape attribute:

class A {
    var closure: (() -> Void)?
    func someMethod(@noescape closure: () -> Void) {
    }
}

Now if you try to write self.closure = closure, it doesn't compile!

Update:

In Swift 3, all closure parameters cannot escape by default. You must add the @escaping attribute in order to make the closure be able to escape from the current scope. This adds a lot more safety to your code!

class A {
    var closure: (() -> Void)?
    func someMethod(closure: @escaping () -> Void) {
    }
}


回答2:

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.



回答3:

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



回答4:

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.