KVO observation not working with Swift generators

If I observe a property using KVO, if the observer is a common class, then I get the following error:

An -observeValueForKeyPath: ofObject: change: context: the message was received but not processed.

The following setting demonstrates the problem concisely. 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") } } 

Create them and try to observe the publisher with the subscriber how this is done (done here inside the UIViewController subclass of the empty project):

 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 a “received but not processed error”. Did I miss something obvious here? Is there anything else I need to do, or generics that shouldn't work with KVO?

+6
source share
1 answer

Description

In general, there are two reasons that may prevent you from using a particular Swift class or method in Objective-C.

First, the pure Swift class uses a C ++-style vtable dispatcher, which is not understood as Objective-C. This can be overcome in most cases by using the dynamic keyword, as you obviously understand.

Second, as soon as generics are generated, Objective-C loses the ability to see any methods of the generic class until it reaches a point in the inheritance hierarchy where the ancestor is not shared. This includes newly introduced methods 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 effectively perceives our nguni variable as an instance of Watusi , and not an instance of Nguni<Int> , which it does not understand at all. Passed by nguni , Objective-C will print "watusi" (instead of "nguni") when the Watusi method is Watusi . (I say “effective” because if you try this and type the class name in Obj-C, it shows _TtC7Divided5Nguni00007FB5E2419A20 , where Divided is the name of my Swift module. Therefore, ObjC certainly “knows” that this is not Watusi .)

Bypass

A workaround is to use thunk, which hides the type parameter. My implementation differs from you in that the generic parameter is an observable class, not a key type. This should be considered as one step above the pseudo-code and is not well smoothed (or well thought out) beyond what is needed in order to get you the gist. (However, I checked 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, but only its init method. Closures are used to “hide” a generic type parameter from Objective-C.

+4
source

Source: https://habr.com/ru/post/978566/


All Articles