Is an "AssociatedObject" property wrapper impossible?

Property wrappers don't give a way to access self, and I couldn't figure out any hacks to try to smuggle it in. Is this code just impossible to properly implement?

import Foundation

@propertyWrapper
struct AssociatedObject<T> {
    var key: UnsafeRawPointer
    var policy: objc_AssociationPolicy

    init(
        initialValue: T,
        key: UnsafeRawPointer,
        policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC
    ) {
        self.key = key
        self.policy = policy
        self.wrappedValue = initialValue
    }
    
    var wrappedValue: T {
        get {
            guard let existingValue = objc_getAssociatedObject(whatDoIPutHereToAccessParent, key) else {
                fatalError("Did not find associated object!")
            }
            guard let correctTypedValue = existingValue as? T else {
                fatalError("Found an associated object that had the wrong type!")
            }
            return correctTypedValue
        }
        set {
            objc_setAssociatedObject(whatDoIPutHereToAccessParent, key, newValue, policy)
        }
    }
}
3 Likes

Hey cukr, thanks for the response, and sorry for the dupe topic. However, I'm kinda struggling with this, I can't figure out how to use that subcript to set an initial value in the init of the property wrapper.

Could you please help me with that?

It's okay, don't worry :) my first reply was so terse, because I was lazy, not because I was annoyed

Oh no, I didn't look close enough at your code. I don't think it's possible :(

You can specify the containing type as part of the generic types of your property wrapper. I don’t have access to my laptop to help create an example though. Because it is a generic type of the property wrapper you can even restrict it to conform to any protocols you require. Would that potentially be enough to do what you are looking for? If so I can try to put together an example tomorrow.

The trick is that you need to pass the host of the associated object into the wrapper after the host has been fully instantiated. Here’s a quick hack that shows the essential technique:


import Foundation

@propertyWrapper
struct AssociatedObject<Host : NSObject, Wrapped> {
    var hasSetHost: Bool = false
    weak var host: Host? = nil {
        willSet {
            precondition(!hasSetHost, "May only set host once")
        }
        didSet {
            hasSetHost = true
            wrappedValue = initialValue
        }
    }
    var initialValue: Wrapped
    var key = 0
    
    init(initialValue: Wrapped) {
        self.initialValue = initialValue
    }
    
    var wrappedValue: Wrapped {
        mutating get {
            host.map {
                objc_getAssociatedObject($0, &key) as! Wrapped
            } ?? initialValue
        }
        set {
            guard let host = host else { 
                initialValue = newValue
                return
            }
            objc_setAssociatedObject(host, &key, newValue, .OBJC_ASSOCIATION_RETAIN)
        }
    }
}

class Example : NSObject {
    @AssociatedObject var other: NSString = "test"
    override init() {
        super.init()
        _other.host = self
    }
}

let ex = Example()
ex.other = "update"
print(ex.other)

This behaves like a box around initialValue until the host is set, at which point it becomes an associated object conduit.

It’s a bit of an odd use case though. If you can add the @AssociatedObject-annotated property, you can add storage to the class itself. So why use an associated object at all?

1 Like

Omg, I didn't even notice this yet. The goal was to use this in extensions to types you can't add stored properties to.

non-static property 'queue' declared inside an extension cannot have a wrapper

Makes sense, unless the wrapper is totally empty

It actually works.
But we always use AssociatedObject in extensions. However propertyWrapper doesn't work in extensions. It's just a struct. Can't have any instance-property storing in extensions.