Binding Swift Things to NSObject Instances
I would like to link Swift things (generics, structs, tuples, anything Objective-C doesn't like) with NSObject instances through extensions.
How can we do this today? objc_setassociatedobject
it doesn't make sense to use Swift functions.
My first approach was to use a weak key global dictionary to store associations. Something like:
struct WeakKey<T where T: NSObject> : Hashable {
weak var object : T!
init (object: T) {
self.object = object
}
var hashValue: Int { return self.object.hashValue }
}
func ==<T where T: NSObject>(lhs: WeakKey<T>, rhs: WeakKey<T>) -> Bool {
return lhs.object == rhs.object
}
typealias ThingObjcDoesntLike = (Int, String)
var _associations : [WeakKey<NSObject>: ThingObjcDoesntLike] = [:]
extension NSObject {
var associatedThing : ThingObjcDoesntLike! {
get {
let key = WeakKey(object: self)
return _associations[key]
}
set(thing) {
let key = WeakKey(object: self)
_associations[key] = thing
}
}
}
let o = NSObject()
let t = (1, "Hello World")
o.associatedThing = t
Sorry, this is a glitch with EXC_BAD_ACCESS
in the ad _associations
. Deletion weak
resolves the failure, but will result in the persistence of the NSObject instances.
For this to be viable, we also need to link to nil
when the NSObject is removed. For this, we can use the associated witness object and override its dealloc to get the job done. For the sake of simplicity, I'm leaving this from the above code.
source to share
How about something like this:
class ObjectWrapper : NSObject {
let value: ThingObjcDoesntLike
init(value: ThingObjcDoesntLike) {
self.value = value
}
}
extension NSObject {
var associatedThing : ThingObjcDoesntLike! {
get {
let wrapper = objc_getAssociatedObject(self, someKey) as ObjectWrapper?
return wrapper?.value
}
set(value) {
let wrapper = ObjectWrapper(value: value)
objc_setAssociatedObject(self, someKey, wrapper, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
}
}
}
source to share
@NickLockwood should be the correct answer. However, while using his approach, I started getting strange memory exceptions at runtime. It might have been a different matter, but the problem went away when I tried a different approach. We'll have to dig further.
Here's what I did in the form of a pad. I am posting it because it works and I find the code interesting even if it is not the best solution.
I am using global Swift Dictionary
with pointers as weak keys. To clear the dictionary, I use a witness-bound object that calls the object associated with deinit
.
import Foundation
import ObjectiveC
var _associations : [COpaquePointer: Any] = [:]
@objc protocol HasAssociatedSwift : class {
func clearSwiftAssociations()
}
var _DeinitWitnessKey: UInt8 = 0
class DeinitWitness : NSObject {
weak var object : HasAssociatedSwift!
init (object: HasAssociatedSwift) {
self.object = object
}
deinit {
object.clearSwiftAssociations()
}
class func addToObject(object : NSObject) {
var witness = objc_getAssociatedObject(object, &_DeinitWitnessKey) as DeinitWitness?
if (witness == nil) {
witness = DeinitWitness(object: object)
objc_setAssociatedObject(object, &_DeinitWitnessKey, witness, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
}
}
}
extension NSObject : HasAssociatedSwift {
var associatedThing : Any! {
get {
return _associations[self.opaquePointer]
}
set(thing) {
DeinitWitness.addToObject(self)
_associations[self.opaquePointer] = thing
}
}
var opaquePointer : COpaquePointer {
return Unmanaged<AnyObject>.passUnretained(self).toOpaque()
}
func clearSwiftAssociations() {
_associations[self.opaquePointer] = nil
}
}
let o = NSObject()
o.associatedThing = (1, "Hello")
source to share