将Swift事物与NSObject实例相关联

时间:2014-09-18 08:09:34

标签: generics swift nsobject

我想通过扩展将Swift事物(泛型,结构,元组,任何Objective-C不喜欢的东西)与NSObject实例相关联。

我们今天怎么办? objc_setassociatedobject对于处理Swift功能毫无用处。

我的第一种方法是使用带有弱键的全局字典来存储关联。类似的东西:

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

很遗憾,这会在EXC_BAD_ACCESS声明中与_associations崩溃。删除weak可以解决崩溃问题,但会导致NSObject实例被保留。

为了使其可行,我们还应该在解除分配NSObject时将关联设置为nil。我们可以使用相关的见证对象,并覆盖其dealloc来完成工作。为简单起见,我将其从上面的代码中删除。

2 个答案:

答案 0 :(得分:1)

这样的事情怎么样:

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 :(得分:1)

@NickLockwood的回答应该是正确的答案。但是,在使用他的方法时,我开始在运行时获得奇怪的内存异常。这可能是另一回事,但当我尝试另一种方法时问题就消失了。必须进一步挖掘。

这是我在Playground表单中所做的。我发布它是因为它似乎有用,我发现代码很有趣,即使它最终不是最好的解决方案。

我使用全局Swift Dictionary,指针为弱键。为了清除字典,我使用见证关联对象,使用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")