La observación de KVO no funciona con los generics Swift

Si observo una propiedad usando KVO, si el observador es una class genérica, entonces recibo el siguiente error:

An -observeValueForKeyPath: ofObject: change: context: el post se recibió pero no se manejó.

La siguiente configuration demuestra el problema sucintamente. Definir algunas classs simples:

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

Instálelos e intente observar al editor con el suscriptor, como así (hecho aquí dentro de una subclass UIViewController de un proyecto en blanco):

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

Si elimino el tipo genérico T de la definición de class, todo funciona bien, pero de lo contrario obtengo el error "recibido pero no controlado". ¿Me estoy perdiendo algo obvio aquí? ¿Hay algo más que necesite hacer o no se supone que los generics funcionen con KVO?

Explicación

Hay dos razones, en general, que pueden evitar que una class o método particular de Swift se use en Objective-C.

La primera es que una class Swift pura utiliza el envío de estilo v ++ de C ++, que Objective-C no entiende. Esto se puede superar en la mayoría de los casos mediante el uso de la palabra key dynamic , como es obvio que entiende.

La segunda es que tan pronto como se presentan los generics, Objective-C pierde la capacidad de ver cualquier método de la class genérica hasta que alcanza un punto en la jerarquía de inheritance donde un antepasado no es genérico. Esto incluye nuevos methods introducidos, así como anulaciones.

 class Watusi : NSObject { dynamic func watusi() { println("watusi") } } class Nguni<T> : Watusi { override func watusi() { println("nguni") } } var nguni = Nguni<Int>(); 

Cuando se pasa a Objective-C, considera nuestra variable nguni efectivamente como una instancia de Watusi , no una instancia de Nguni<Int> , que no comprende en absoluto. Aprobado un nguni , Objective-C imprimirá "watusi" (en lugar de "nguni") cuando se watusi método watusi . (Digo "efectivamente" porque si testing esto e imprime el nombre de la class en Obj-C, muestra _TtC7Divided5Nguni00007FB5E2419A20 , donde Divided es el nombre de mi module Swift. Por lo tanto, ObjC es ciertamente "consciente" de que esto no es un Watusi .)

Solución alternativa

Una solución es utilizar un thunk que oculte el parámetro de tipo genérico. Mi implementación difiere de la suya en que el parámetro genérico representa la class observada, no el tipo de la key. Esto debe considerarse como un paso por encima del pseudocódigo y no está bien estructurado (o bien pensado) más allá de lo que se necesita para get lo esencial. (Sin embargo, lo probé.)

 class Subscriber : NSObject { private let _observe : (String, AnyObject, [NSObject: AnyObject], UnsafeMutablePointer<Void>) -> Void requinetworking 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!" 

Esto funciona porque el Subscriber sí no es genérico, solo su método init . Los cierres se utilizan para "ocultar" el parámetro de tipo genérico de Objective-C.