ios - KVO 观察不适用于 Swift 泛型

标签 ios generics swift key-value-observing

如果我使用 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/

相关文章:

ios - 如何获取从 Storyboard实例化的 UIButton 类中的 View Controller 实例

android - 无法创建应用程序,Phonegap 中的文件类型无效

java - 尝试使用泛型确保映射中的 N 参数

swift - 在 Swift 中创建一个接受 native Swift 协议(protocol)的弱容器

swift - 协议(protocol) associatedType 和 <>

ios - Core Data 对我手动编写的 @NSManaged 属性做了什么。我得到实体而不是键值赞美

iphone - UIView 子类的 Dealloc 方法没有被调用

java - 指定类对象的类型

java - 将 java 对象转换为具有已知类型参数的泛型类型

ios - HealthKit:保存 HKCategoryTypeIdentifierAppleStandHour 类型的样本