Updating closures to Swift 3 - @escaping
Asked Answered
B

2

75

I've updated my code to Xcode 8.0 beta 6 but I got stuck with what seems to be about the new non escaping closure default. In the following code Xcode suggests to add @escaping in front of completion: in the first line of the below code, but that still won't compile and goes in circles. *

(EDIT: In fact, @escaping should be added in after completion:, as Xcode suggests. The alert may still show but cleaning and compiling will remove it.)* How should this code be re-written / fixed to work in the updated Swift 3? I've had a look in the new manual but I couldn't find proper code samples.

func doSomething(withParameter parameter: Int, completion: () -> ()) {
    // Does something

    callSomeOtherFunc(withCompletion: completion)
  }

// Calling the method and execute closure 
doSomething(withParameter: 2) {
  // do things in closure
}

Any help much appreciated!

Bigot answered 21/8, 2016 at 11:6 Comment(0)
M
58

Swift 3: closure parameter attributes are now applied to the parameter type, and not the parameter itself

Prior to Swift 3, the closure attributes @autoclosure and @noescape used to be attributes to the closure parameter, but are now attributes to the parameter type; see the following accepted Swift evolution proposal:

Your specific question pertain to parameter type attribute @escaping (for which the same new rule applies), as described in the accepted Swift evolution proposal to let closure parameters be non-escaping by default:

These proposals are now both implemented in the beta stage of Xcode 8 (see release notes for Xcode 8 beta 6; dev. account login needed for access)

New in Xcode 8 beta 6 - Swift Compiler: Swift Language

Closure parameters are non-escaping by default, rather than explicitly being annotated with @noescape. Use @escaping to indicate that a closure parameter may escape. @autoclosure(escaping) is now written as @autoclosure @escaping. The annotations @noescape and @autoclosure(escaping) are deprecated. (SE-0103)

...

New in Xcode 8 beta – Swift and Apple LLVM Compilers: Swift Language

The @noescape and @autoclosure attributes must now be written before the parameter type instead of before the parameter name. [SE-0049]

Hence, you use the non-default @escaping attribute as follows; applied to the type of the closure parameter, rather than the parameter itself

func doSomething(withParameter parameter: Int, completion: @escaping () -> ()) {
    // ...
}

(Including my answer to a question in an upvoted comment below, as comments are not persistent data on SO)

@Cristi Băluță: "What does escaping do? Never seen this keywords before swift3 auto-conversion ... "

See e.g. the link to the SE-0103 evolution proposal above (as well as the quoted text from the beta 6 release notes): previously, closure parameters were escaping by default (hence no need for the existence of an explicit annotation for escaping), but are now instead non-escaping, by default. Hence the addition of @escaping to explicitly annotate that a closure parameter may escape (contrary to its default behaviour). This also explains why @noescape is now deprecated (no need to annotate the default behaviour).

For explaining what it means that a closure parameter is escaping, I quote the Language Reference - attributes:

"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."

Muna answered 21/8, 2016 at 12:5 Comment(9)
Thanks for the thorough answer dfri. I actually did add @escaping in the right place before the parameter, I just noticed my mistake pointing that out in the description. Xcode would still complain as I described, but doing a clean up and compiling anyways finally removes the alerts.Bigot
@Bigot happy to help.Muna
What does escaping do? Never seen this keywords before swift3 auto-conversion and i don't think i lost anything.Electrostatics
@CristiBăluță see e.g. the link to the SE-0103 evolution proposal above (as well as the quoted text from the beta 6 release notes): previously, closure parameters were escaping by default (hence no need for a annotation that they were escaping), but are now instead non-escaping. Hence the addition of @escaping to explicitly annotate that a close parameter may escape (contrary to its default behaviour). This also explains why @noescape is no deprecated (no need to annotate default behaviour).Muna
But that still doesn't answer @CristiBăluță's answer. To me escaping sounds like running away & gives me feel like closure-function is going to run away :P. what does Escaping do?Interruption
@SagarR.Kothari Both the question and the answer is based on the fact that we know the difference between escaping and nonescaping closures, hence my previous comment to CristiBăluță (answers why this keyword now exists). For explaining what it does, I quote the lang. ref.: "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.".Muna
@SagarR.Kothari For details, refer to the Language Reference - Attributes, as well as the Language Guide - Closures (see section Escaping Closures).Muna
Now it's very clear what it does, thanks. So they basically switched favouring one kind of programmer to the other, i'm the one affected because i pass closures in order to call them asynchronously, so i need escaping. I hope in the future they'll guess what i want to do with the closure and mark it for me.Electrostatics
Why if a closure parameter is optional I don´t have to mark it as @escaping?Slotter
H
23

@noescape

From xcode 8 beta 6 @noescape is the default. Prior to that, @escaping was the default. Anybody updating to swift 3.0 from previous versions might face this error.

You can not store a @noescape closure inside a variable. Because if you can store a closure inside a variable, you can execute the closure from anywhere in your code. But @noescape states that the closure parameter can not escape the body of the function.

This will give compiler error in Xcode 8

class MyClass {

    var myClosure: (() -> ())?

    func doSomething(finishBlock: () -> ()) {
        myClosure = finishBlock    // ‼️ Error: Assigning non-escaping parameter 'finishBlock' to an @escaping closure
    }
}

This will compile ok (explicitly write @escaping)

class MyClass {

    var myClosure: (() -> ())?

    func doSomething(finishBlock: @escaping () -> ()) {
        myClosure = finishBlock
    }
}

Benefits of @noescape:

  • Compiler can optimize your code for better Performance
  • Compiler can take care of memory management
  • There is no need to use a weak reference to self in the closure


For details check out: Make non-escaping closures the default

Habakkuk answered 24/10, 2016 at 10:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.