如果我使用 KVO 观察一个属性,如果观察者是一个泛型类,那么我会收到以下错误:
An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
以下设置简洁地演示了该问题。定义一些简单的类:
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")
}
}
实例化它们并尝试观察发布者和订阅者,就像这样(在空白项目的 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"
}
如果我从类定义中删除通用类型 T 那么一切正常,但否则我会收到“已收到但未处理的错误”。我在这里遗漏了一些明显的东西吗?还有什么我需要做的吗,或者泛型不应该与 KVO 一起工作?
最佳答案
解释
一般来说,有两个原因可以阻止特定的 Swift 类或方法在 Objective-C 中使用。
首先是纯 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>();
当传递给 Objective-C 时,它认为我们的 nguni
作为 Watusi
的实例有效地变量, 不是 Nguni<Int>
的实例,它根本不理解。通过了 nguni
, Objective-C 将在 watusi
时打印“watusi”(而不是“nguni”)方法被调用。 (我说“有效”是因为如果你尝试这个并在 Obj-C 中打印类的名称,它会显示 _TtC7Divided5Nguni00007FB5E2419A20
,其中 Divided
是我的 Swift 模块的名称。所以 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!"
这是有效的,因为 Subscriber
本身不是通用的,只有它的 init
方法。闭包用于从 Objective-C 中“隐藏”泛型类型参数。
关于ios - KVO 观察不适用于 Swift 泛型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27087718/