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)
}
}
}
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.
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?
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.