KVO观察不适用于Swift泛型

时间:2014-11-23 10:01:13

标签: ios generics swift key-value-observing

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

  

-observeValueForKeyPath:ofObject:change:context:message was   收到但未处理。

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

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,那么一切正常,但是否则我得到了#34;收到但未处理错误&#34;。我错过了一些明显的东西吗?还有其他我需要做的事情,还是不应该使用KVO的仿制药?

1 个答案:

答案 0 :(得分:5)

说明

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

首先,纯Swift类使用C ++样式的vtable dispatch,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将打印&#34; watusi&#34; (而不是&#34; nguni&#34;)调用watusi方法时。 (我说&#34;有效&#34;因为如果你尝试这个并在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方法。闭包用于&#34;隐藏&#34; Objective-C中的泛型类型参数。