177

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.

pkamb
  • 33,281
  • 23
  • 160
  • 191
Lescai Ionel
  • 4,216
  • 3
  • 30
  • 48
  • 25
    "Removing the `@escaping` attribute, the code compiles and runs" - That's because, as described in [SR-2444](https://bugs.swift.org/browse/SR-2444), `Action?` is, by default, escaping. So, removing `@escaping` when using the optional closure accomplishes what you need. – Rob Sep 21 '16 at 14:40
  • Related: [Updating closures to Swift 3 - @escaping](http://stackoverflow.com/questions/39063499/updating-closures-to-swift-3-escaping/). – dfrib Sep 21 '16 at 14:57
  • type alias closures are escaping – Masih Oct 13 '17 at 20:03
  • 2
    Here's an [excellent article by Ole Begemann](https://oleb.net/blog/2016/10/optional-non-escaping-closures/) that describes why it's happening and some workarounds if you want optional parameters to be @noescape. – Senseful May 31 '18 at 06:53

5 Answers5

256

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.

Dmitrii Cooler
  • 4,032
  • 1
  • 22
  • 21
  • 9
    I think this adds most important information to the subject, it should be accepted answer. – Damian Dudycz Oct 22 '17 at 18:38
  • 1
    The reasoned answer. How probable is it that this could change? – GoldenJoe Nov 13 '17 at 10:27
  • 4
    This makes sense since technically saying `(()->Void)?` is the same as saying you have `Optional<()->Void>` and in order for the `Optional` to maintain ownership it would have to only accept `@escaping` functions. I'll third that this should be the accepted answer. Thank you. – Dean Kelly Feb 23 '18 at 03:00
134

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.

Jans
  • 11,064
  • 3
  • 37
  • 45
  • 5
    Now getting `@escaping may only be applied to parameters of function type func doStuff(stuff: String, completion: (@escaping ()->())?) {` – Lescai Ionel Sep 21 '16 at 14:24
  • 1
    `a temporary solution is remove the @escaping attribute from optional function type, that keep the function as escaping.` Can you explain this further? The default semantic in swift 3 is non-escaping. Although it compiles without @escaping I'm afraid it will cause problems by being treated as non-escaping. Is that not true? – Pat Niemeyer Nov 29 '16 at 21:00
  • 49
    Upon further reading I see that SR-2444 says that all optional closures are treated as escaping, which is a complementary bug :) I going to assume that when it is fixed the compile will warn us to make the change. – Pat Niemeyer Nov 29 '16 at 21:09
  • Maybe a little off topic; but how does this work for `@autoclosure`? One gets the same error there... – Gerald Eersteling Jun 01 '17 at 13:57
  • it's working without @escaping __ func doStuff(stuff: String, completion: (()->())?) { – Феннур Мезитов Mar 14 '18 at 16:45
26

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)
}
pkamb
  • 33,281
  • 23
  • 160
  • 191
Freeman Man
  • 301
  • 2
  • 5
18

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

func doStuff(stuff: String, completion: (()->())? ) {
    print(stuff)
    action = completion
    completion?()
}
Igor
  • 12,165
  • 4
  • 57
  • 73
5

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.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Hi @matt, Thanks for the explanation Are both same? ~ func foo(_ closure: @ escaping () -> Void) {} ~ func bar(_ closure: ( () -> Void)? ) {} – Roshan Sah Dec 03 '20 at 14:02