KVO观察不适用于Swift泛型

chr*_*838 6 generics key-value-observing ios swift

如果我使用KVO观察属性,如果观察者是泛型类,那么我收到以下错误:

-observeValueForKeyPath:ofObject:change:context:收到消息但未处理.

以下设置简洁地演示了该问题.定义一些简单的类:

var context = "SomeContextString"

class Publisher : NSObject {
    dynamic var observeMeString:String = "Initially this value"
}

class Subscriber<T> : NSObject {
    override func observeValueForKeyPath(keyPath: String,
                    ofObject object: AnyObject,
                    change: [NSObject : AnyObject],
                    context: UnsafeMutablePointer<Void>) {
        println("Hey I saw something change")
    }
}
Run Code Online (Sandbox Code Playgroud)

实例化它们并尝试与订阅者一起观察发布者,就像这样(在空白项目的UIViewController子类中完成):

var pub = Publisher()
var sub = Subscriber<String>()

override func viewDidLoad() {
    super.viewDidLoad()

    pub.addObserver(sub, forKeyPath: "observeMeString", options: .New, context: &context)
    pub.observeMeString = "Now this value"
}
Run Code Online (Sandbox Code Playgroud)

如果我从类定义中删除泛型类型T然后一切正常,但否则我得到"收到但未处理的错误".我错过了一些明显的东西吗?还有其他我需要做的事情,还是仿制品不应该与KVO合作?

Gre*_*ley 5

说明

通常,有两个原因可以阻止在Objective-C中使用特定的Swift类或方法.

第一个是纯Swift类使用C++样式的vtable调度,Objective-C无法理解.在大多数情况下,通过使用dynamic关键字可以克服这一点,正如您显而易见的那样.

第二个是,只要引入了泛型,Objective-C就无法查看泛型类的任何方法,直到它到达祖先不是通用的继承层次结构中的某个点.这包括引入的新方法以及覆盖.

class Watusi : NSObject {
    dynamic func watusi() {
        println("watusi")
    }
}

class Nguni<T> : Watusi {
    override func watusi() {
       println("nguni")
    }
}

var nguni = Nguni<Int>();
Run Code Online (Sandbox Code Playgroud)

当传递给Objective-C时,它将我们的nguni变量有效地视为一个实例Watusi,而不是Nguni<Int>它根本不理解的实例.通过a nguni,Objective-C将在watusi调用方法时打印"watusi"(而不是"nguni").(我说"有效",因为如果你试试这个,打印ObjC类的名称,它表明_TtC7Divided5Nguni00007FB5E2419A20,在那里Divided是我的雨燕模块的名称,所以ObjC肯定是"知道",这是不是一个Watusi.)

解决方法

解决方法是使用隐藏泛型类型参数的thunk.我的实现与您的实现不同,泛型参数表示被观察的类,而不是键的类型.这应该被视为超过伪代码的一步,并且不能很好地充实(或深思熟虑)超出了获得要点所需的内容.(但是,我测试了它.)

class Subscriber : NSObject {
    private let _observe : (String, AnyObject, [NSObject: AnyObject], UnsafeMutablePointer<Void>) -> Void

    required init<T: NSObject>(obj: T, observe: ((T, String) -> Void)) {
        _observe = { keyPath, obj, changes, context in
            observe(obj as T, keyPath)
        }
    }
    override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
        _observe(keyPath, object, change, context)
    }
}

class Publisher: NSObject {
    dynamic var string: String = nil
}

let publisher = Publisher()
let subscriber = Subscriber(publisher) { _, _ in println("Something changed!") }
publisher.addObserver(subscriber, forKeyPath: "string", options: .New, context: nil)
publisher.string = "Something else!"
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为Subscriber它本身不是通用的,只有它的init方法.闭包用于从Objective-C"隐藏"泛型类型参数.