Property Wrappers: Access to both enclosing 'self' and wrapper instance

A key missing (public) feature of Property Wrappers is referencing the enclosing self; that is, the object that owns the property which it's wrapping.

Here's an example question from StackOverflow exploring some alternatives: Can a Swift Property Wrapper reference the owner of the property its wrapping?.

It's is also referenced as a future direction in SE-0258: Referencing the enclosing 'self' in a wrapper type. This design was actually implemented but not made public for the Combine framework:

static subscript<EnclosingSelf>(
      _enclosingInstance observed: EnclosingSelf,
      wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
      storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Self>
    ) -> Value

I've implemented this method, but there's one big downside as far as I can tell: while the subscript method does provide access to the enclosing self, the subscript method is static, so you don't have access to the instance of the property wrapper. This means that if you provide a custom init to your wrapper like so:

init(_ str: String) {
    self.str = str
}
private let str: String

and initialize it as such:

@Wrapper("helloWorld") var someProperty: Int?

in the implementation of the static subscript you cannot access the wrapper's private instance variable str. You can imagine a lot of uses of simultaneously referencing both the enclosing self instance and the wrapper instance, like implementing your own theming system.

I'm wondering if I'm overlooking some way here of accessing the wrapper instance in this static subscript?

I‘m not sure I fully understand you but if you want to get back the property wrapper itself then this should do the trick.

observed[keyPath: storageKeyPath].str

Anyways, this is private API and subject to change so I would avoid relying on it if you want to prevent breakage in the future.

1 Like

Yes, that's what I'm looking for, thank you! But, I'm having trouble getting the compiler to pass. This wrapper implementation alone compiles just fine:

@propertyWrapper
public final class Wrapper {
    
    public static subscript<EnclosingSelf>(
      _enclosingInstance observed: EnclosingSelf,
      wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Int?>,
      storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Self>
    ) -> Int? {
      get {
        return observed[keyPath: storageKeyPath].stored
      }
      set {
        let oldValue = observed[keyPath: storageKeyPath].stored
        if newValue != oldValue {
            // TODO: call wrapper instance with enclosing self
        }
        observed[keyPath: storageKeyPath].stored = newValue
      }
    }
    
    public var wrappedValue: Int? {
      get { fatalError("called wrappedValue getter") }
      set { fatalError("called wrappedValue setter") }
    }
    
    public init(str: String) {
        self.str = str
    }
    
    // MARK: - Private
    
    private let str: String
    private var stored: Int?
}

But when I add it to a class, the compiler fails for an unknown reason:

open class TestView: UIView {
    @Wrapper(str: "HelloWorld") public var testProp: Int?
}

/** 
    Swift Compiler Error
    Type '_' has no member 'testProp'
*/

Try to swap Self with Wrapper and check if it works then. If it does, file a bug at bugs.swift.org. This could be related to Self on classes not properly respecting final keyword. If it does not work then it must be something else.

Changing Self to Wrapper worked, thanks!!

https://bugs.swift.org/browse/SR-12023

1 Like

@suyashsrijan regarding your comment on Jira. Shouldn‘t this example still pass because the class is marked as final?

Yeah, Self and TypeName should be equivalent in a final class, but it's just not supported at the moment (I opened a PR then forgot about it, so sorry about that. I'll try to find some time soon). I think there's a dup for this, so I'll update the JIRA ticket when I find it.

1 Like

Possibly this one? https://bugs.swift.org/browse/SR-11176

Yeah or https://bugs.swift.org/browse/SR-11414. Seems like there are a few dupes!

Terms of Service

Privacy Policy

Cookie Policy