19

I've noticed that in Swift 2.2, closures marked as non-escaping with @noescape do not require an explicit self. In Swift 3, all closures are non-escaping by default and now requires them to be marked with @escaping if you want them be able to escape.

Given that all of the closures in Swift 3 by default are non-escaping, why do they require an explicit self?

final class SomeViewController: NSViewController {

    var someClosure: () -> () = { _ in }

    override func viewDidLoad() {
        super.viewDidLoad()

        someClosure = {
            view.layer = CALayer() // ERROR: Implicit use of `self` in closure; use `self.` to make capture semantics explicit
        }
    }
}
mfaani
  • 33,269
  • 19
  • 164
  • 293
beingadrian
  • 441
  • 5
  • 10

3 Answers3

14

In Swift 3, all closures are non-escaping by default

No, in Swift 3, only closure function arguments (i.e function inputs that are functions themselves) are non-escaping by default (as per SE-0103). For example:

class A {

    let n = 5
    var bar : () -> Void = {}

    func foo(_ closure: () -> Void) {
        bar = closure // As closure is non-escaping, it is illegal to store it.
    }

    func baz() {
        foo {
            // no explict 'self.' required in order to capture n,
            // as foo's closure argument is non-escaping,
            // therefore n is guaranteed to only be captured for the lifetime of foo(_:)
            print(n)
        }
    }
}

As closure in the above example is non-escaping, it is prohibited from being stored or captured, thus limiting its lifetime to the lifetime of the function foo(_:). This therefore means that any values it captures are guaranteed to not remain captured after the function exits – meaning that you don’t need to worry about problems that can occur with capturing, such as retain cycles.

However, a closure stored property (such as bar in the above example) is by definition escaping (it would be nonsensical to mark it with @noescape) as its lifetime not limited to a given function – it (and therefore all its captured variables) will remain in memory as long as the given instance remains in memory. This can therefore easily lead to problems such as retain cycles, which is why you need to use an explicit self. in order to make the capturing semantics explicit.

In fact, case in point, your example code will create a retain cycle upon viewDidLoad() being called, as someClosure strongly captures self, and self strongly references someClosure, as it's a stored property.

It's worth noting that as an extension of the "stored function properties are always escaping" rule, functions stored in aggregates (i.e structures and enumerations with associated values) are also always escaping, as there's no restrictions on what you do with such aggregates. As pointed out by pandaren codemaster, this currently includes Optional – meaning that Optional<() -> Void> (aka. (() -> Void)?) is always escaping. The compiler might eventually make this a special case for function parameters though, given that optional is already built on a lot of compiler magic.


Of course, one place where you would expect to be able to use the @noescape attribute is on a closure that’s a local variable in a function. Such a closure would have a predictable lifetime, as long as it isn’t stored outside of the function, or captured. For example:

class A {

    let n = 5

    func foo() {

        let f : @noescape () -> Void = {
            print(n)
        }

        f()
    }
}

Unfortunately, as @noescape is being removed in Swift 3, this won't be possible (What's interesting is that in Xcode 8 GM, it is possible, but yields a deprecation warning). As Jon Shier says, we’ll have to wait for it to be re-added to the language, which may or may not happen.

Hamish
  • 78,605
  • 19
  • 187
  • 280
  • Thank you for the clarification! Just to clarify, when the view controller itself is deallocated, would the closure and the view be deallocated as well, or would they cause a memory leak? – beingadrian Sep 15 '16 at 16:34
  • @beingadrian If your `someClosure` property is assigned a closure that strongly captures `self` (such as in your example in `viewDidLoad`), then it will indeed create a retain cycle, and will therefore leak if there are no other strong references to your view controller (you can verify this yourself by implementing `deinit` with a `print` statement in your view controller class). The simplest solution would be to capture `self` as `weak` (define the closure as `{[weak self] in ...}`). You could then use optional chaining in the closure to assign the view's layer, `self?.view.layer = CALayer()` – Hamish Sep 15 '16 at 16:50
  • @beingadrian Another potential solution is to capture `self` in the closure as `unowned`, however this is potentially dangerous as it will crash if the closure is invoked after the view controller instance is deallocated. Therefore I would only advise doing this if `someClosure` was a `private` property (then there's no chance of another class getting a reference to it). – Hamish Sep 15 '16 at 16:54
  • For more info about `weak` and `unowned` capturing, see the [Swift Language Guide on "Resolving Strong Reference Cycles Between Class Instances"](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-ID52) – Hamish Sep 15 '16 at 16:54
  • As a final note, if your closure is using `self` multiple times, you may wish to introduce a new local variable that will keep `self` strongly captured for the duration of the closure exection (to ensure it doesn't deallocate halfway through). With `weak` capturing, one popular way of doing this is through using a `guard` – for example, `guard let strongSelf = self else { return }`. You would then use `strongSelf` in the closure in place of `self` (it also eliminates the need for optional chaining). – Hamish Sep 15 '16 at 17:02
  • Fantastic insight. Thank you for the explanations. They're extremely helpful! – beingadrian Sep 16 '16 at 06:10
  • I think, you should also add "optional closures are implicitly escaping" to this great answer. @Hamish – Okhan Okbay Aug 03 '18 at 21:26
  • @pandarencodemaster Good idea, thanks! Edited answer. – Hamish Aug 06 '18 at 12:52
5

Stored closures are considered escaping by default, even when they aren't really. There's no way to mark them non-escaping so we're stuck like this until they add @noescape back to the language, which they may or may not do. See this discussion on the swift-evolution mailing list.

Jon Shier
  • 12,200
  • 3
  • 35
  • 37
-1

When you make a parameter with the @autoclosure attribute, the expression you pass as an argument is automatically wrapped into a closure for you. It makes your code cleaner. The @noescape keyword is more complex and interesting. It can be applied to a function parameter with any function type. The @noescape attribute indicates that a closure will be used inside a function body, before the function return is called. It means it won’t escape the function body.

Marc Steven
  • 477
  • 4
  • 16