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.

+3


source to share


2 answers


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

      

+1


source


Answer to

@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")

      

+1


source







All Articles