Swift optional escaping closure parameter

SwiftFunctionClosuresOptional

Swift Problem Overview


Given:

typealias Action = () -> ()

var action: Action = { }

func doStuff(stuff: String, completion: @escaping Action) {
    print(stuff)
    action = completion
    completion()
}

func doStuffAgain() {
    print("again")
    action()
}

doStuff(stuff: "do stuff") { 
    print("swift 3!")
}

doStuffAgain()

Is there any way to make the completion parameter (and action) of type Action? and also keep @escaping ?

Changing the type gives the following error:

> @escaping attribute only applies to function types

Removing the @escaping attribute, the code compiles and runs, but doesn't seem to be correct since the completion closure is escaping the scope of the function.

Swift Solutions


Solution 1 - Swift

from: swift-users mailing list

> Basically, @escaping is valid only on closures in function parameter position. The noescape-by-default rule only applies to these closures at function parameter position, otherwise they are escaping. Aggregates, such as enums with associated values (e.g. Optional), tuples, structs, etc., if they have closures, follow the default rules for closures that are not at function parameter position, i.e. they are escaping.

So optional function parameter is @escaping by default.
@noeascape only apply to function parameter by default.

Solution 2 - Swift

There is a SR-2552 reporting that @escaping is not recognizing function type alias. that's why the error @escaping attribute only applies to function types. you can workaround by expanding the function type in the function signature:

typealias Action = () -> ()

var action: Action? = { }

func doStuff(stuff: String, completion: (@escaping ()->())?) {
    print(stuff)
    action = completion
    completion?()
}

func doStuffAgain() {
    print("again")
    action?()
}

doStuff(stuff: "do stuff") {
    print("swift 3!")
}

doStuffAgain()

EDIT 1::

I was actually under a xcode 8 beta version where the bug SR-2552 was not resolved yet. fixing that bug, introduced a new one(the one you're facing) that is still open. see SR-2444.

The workaround @Michael Ilseman pointed as a temporary solution is remove the @escaping attribute from optional function type, that keep the function as escaping.

func doStuff(stuff: String, completion: Action?) {...}

EDIT 2::

The SR-2444 has been closed stating explicitly that closures in parameters positions are not escaping and need them to be marked with @escaping to make them escaping, but the optional parameters are implicitly escaping, since ((Int)->())? is a synonyms of Optional<(Int)->()>, optional closures are escaping.

Solution 3 - Swift

I ran into a similar problem because mixing @escaping and non-@escaping is very confusing, especially if you need to pass the closures around.

I ended up assigning a no-op default value to the closure parameter via = { _ in }, which I think makes more sense:

func doStuff(stuff: String = "do stuff",
        completion: @escaping (_ some: String) -> Void = { _ in }) {
     completion(stuff)
}

doStuff(stuff: "bla") {
    stuff in
    print(stuff)
}

doStuff() {
    stuff in
    print(stuff)
}

Solution 4 - Swift

I got it working in Swift 3 without any warnings only this way:

func doStuff(stuff: String, completion: (()->())? ) {
    print(stuff)
    action = completion
    completion?()
}

Solution 5 - Swift

The important thing to understand in the example is that if you change Action to Action? the closure is escaping. So, let's do what you propose:

typealias Action = () -> ()

var action: Action? = { }

func doStuff(stuff: String, completion: Action?) {
    print(stuff)
    action = completion
    completion?()
}

Okay, now we'll call doStuff:

class ViewController: UIViewController {
    var prop = ""
    override func viewDidLoad() {
        super.viewDidLoad()
        doStuff(stuff: "do stuff") {
            print("swift 3!")
            print(prop) // error: Reference to property 'prop' in closure 
                        // requires explicit 'self.' to make capture semantics explicit
        }
    }
}

Well, that requirement only arises for escaping closures. So the closure is escaping. That's why you don't mark it escaping - it's escaping already.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLescai IonelView Question on Stackoverflow
Solution 1 - SwiftDmitrii CoolerView Answer on Stackoverflow
Solution 2 - SwiftJansView Answer on Stackoverflow
Solution 3 - SwiftFreeman ManView Answer on Stackoverflow
Solution 4 - SwiftIgorView Answer on Stackoverflow
Solution 5 - SwiftmattView Answer on Stackoverflow