简体   繁体   English

KVO观察不适用于Swift泛型

[英]KVO observation not working with Swift generics

If I observe a property using KVO, if the observer is a generic class then I receive the following error: 如果我使用KVO观察属性,如果观察者是泛型类,那么我收到以下错误:

An -observeValueForKeyPath:ofObject:change:context: message was received but not handled. -observeValueForKeyPath:ofObject:change:context:收到消息但未处理。

The following setup demonstrates the problem succinctly. 以下设置简洁地演示了该问题。 Define some simple classes: 定义一些简单的类:

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")
    }
}

Instantiate them and try to observe the publisher with the subscriber, like so (done here inside a UIViewController subclass of a blank project): 实例化它们并尝试与订阅者一起观察发布者,就像这样(在空白项目的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"
}

If I remove the generic type T from the class definition then everything works fine, but otherwise I get the "received but not handled error". 如果我从类定义中删除泛型类型T然后一切正常,但否则我得到“收到但未处理的错误”。 Am I missing something obvious here? 我错过了一些明显的东西吗? Is there something else I need to do, or are generics not supposed to work with KVO? 还有其他我需要做的事情,还是仿制品不应该与KVO合作?

Explanation 说明

There are two reasons, in general, that can prevent a particular Swift class or method from being used in Objective-C. 通常,有两个原因可以阻止在Objective-C中使用特定的Swift类或方法。

The first is that a pure Swift class uses C++-style vtable dispatch, which is not understood by Objective-C. 第一个是纯Swift类使用C ++风格的vtable调度,Objective-C无法理解。 This can be overcome in most cases by using the dynamic keyword, as you obviously understand. 在大多数情况下,通过使用dynamic关键字可以克服这一点,正如您显而易见的那样。

The second is that as soon as generics are introduced, Objective-C looses the ability to see any methods of the generic class until it reaches a point in the inheritance hierarchy where an ancestor is not generic. 第二个是,只要引入了泛型,Objective-C就无法查看泛型类的任何方法,直到它到达祖先不是通用的继承层次结构中的某个点。 This includes new methods introduced as well as overrides. 这包括引入的新方法以及覆盖。

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

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

var nguni = Nguni<Int>();

When passed to Objective-C, it regards our nguni variable effectively as an instance of Watusi , not an instance of Nguni<Int> , which it does not understand at all. 当传递给Objective-C的,它认为我们的nguni有效变量的一个实例Watusi ,而不是一个实例Nguni<Int> ,它不明白的。 Passed an nguni , Objective-C will print "watusi" (instead of "nguni") when the watusi method is called. 通过一个nguni ,当调用watusi方法时,Objective-C将打印“watusi”(而不是“nguni”)。 (I say "effectively" because if you try this and print the name of the class in Obj-C, it shows _TtC7Divided5Nguni00007FB5E2419A20 , where Divided is the name of my Swift module. So ObjC is certainly "aware" that this is not a Watusi .) (我说“有效”,因为如果你尝试这个并在Obj-C中打印类的名称,它会显示_TtC7Divided5Nguni00007FB5E2419A20 ,其中Divided是我的Swift模块的名称。所以ObjC当然“意识到”这不是一个Watusi 。)

Workaround 解决方法

A workaround is to use a thunk that hides the generic type parameter. 解决方法是使用隐藏泛型类型参数的thunk。 My implementation differs from yours in that the generic parameter represents the class being observed, not the type of the key. 我的实现与您的实现不同,泛型参数表示被观察的类,而不是键的类型。 This should be regarded as one step above pseudocode and is not well fleshed out (or well thought out) beyond what's needed to get you the gist. 这应该被视为超过伪代码的一步,并且不能很好地充实(或深思熟虑)超出获得要点所需的内容。 (However, I did test it.) (但是,我测试了它。)

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!"

This works because Subscriber itself is not generic, only its init method. 这是有效的,因为Subscriber本身不是通用的,只有它的init方法。 Closures are used to "hide" the generic type parameter from Objective-C. 闭包用于从Objective-C“隐藏”泛型类型参数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM