Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to capture properties of `self` without capturing `self`?

Tags:

swift

You can copy this playground verbatim:

var closures=[() -> Void]()
class Thing {
    let name: String
    init(_ name: String) { self.name=name }
}
class RefThingWrapper {
    let thing: Thing
    init(_ t: Thing) {
        self.thing=t
        closures.append { [thing, weak self] in   // Even though `thing` captured strongly, `self` could still get deallocated.
            print((self == nil) ? "`self` deallocated" : "`self` not deallocated")
            print(thing.name)   // Decided to use `thing` within closure to eliminate any chance of optimizations affecting the test results — which I found when I captured `self` strongly without using it.
        }
    }
}
struct ValThingWrapper {
    let thing: Thing
    init(_ t: Thing) {
        self.thing=t
        closures.append { [weak thing] in
            print((thing == nil) ? "`thing` deallocated" : "`thing` not deallocated")
        }
    }
}

var wrapper=Optional(RefThingWrapper(Thing("Thing 1")))   // Test with reference type.
//var wrapper=Optional(ValThingWrapper(Thing("Thing 1")))   // Test with value type.
closures[0]()
wrapper=nil
closures[0]()

It demonstrates how a property of self — whether self is a reference or value type — can be captured within a closure independently of self. Running the program as is demonstrates a captured property existing after self has been deallocated. Testing with the value type wrapper demonstrates that, if weakly captured, the instance will be deallocated once the referencing value instance is deallocated.

I wasn't sure this was possible because when creating the closure at first, I forgot to initialize the property I was capturing. So the compiler complained — in the capture list — 'self' used before all stored properties are initialized. So I figured self was being captured implicitly, and only after digging deeper discovered otherwise.

Is this documented somewhere? I found this post by Joe Groff where he proposes:

For 'let' properties of classes, it'd be reasonable to propose having closures capture the property directly by default in this way instead of capturing 'self' (and possibly allowing referencing them without 'self.', since 'self' wouldn't be involved in any cycle formed this way).

This was back in 2015, and I didn't find any implemented proposals that arose from the discussion. Is there any authoritative source that communicates this behavior?

like image 590
shoe Avatar asked Sep 14 '25 23:09

shoe


1 Answers

If you’re just asking for documentation on capture lists and reference types, see The Swift Programming Language Resolving Strong Reference Cycles for Closures. Also see the Language Reference: Capture Lists

  • If your capture list includes value type, you’re getting copy of the value.

    var foo = 1
    
    let closure = { [foo] in
        print(foo)
    }
    
    foo = 42
    
    closure() // 1; capturing value of `foo` as it was when the closure was declared
    
  • If your capture list includes a reference type, you’re getting a copy of the reference to that current instance.

    class Bar {
        var value: Int
    
        init(value: Int) { self.value = value }
    }
    
    var bar = Bar(value: 1)
    
    let closure = { [bar] in
        print(bar.value)
    }
    
    bar.value = 2
    
    bar = Bar(value: 3)
    
    closure() // 2; capturing reference to first instance that was subsequently updated
    

    These captured references are strong by default, but can optionally be marked as weak or unowned, as needed, too.

  • That Capture Lists document outlines the way that you can capture a property without capturing self:

    You can also bind an arbitrary expression to a named value in a capture list. The expression is evaluated when the closure is created, and the value is captured with the specified strength. For example:

    // Weak capture of "self.parent" as "parent"
    myFunction { [weak parent = self.parent] in print(parent!.title) }
    

    I’m not crazy about their code sample, but it illustrates the capture of a property without capturing self, nonetheless.

like image 142
Rob Avatar answered Sep 17 '25 21:09

Rob



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!