Tengo el siguiente código que agrega un observador en dos propiedades:
obs = [] let xa = self.observe(\CirclePolygon.radius, options: [.new]) { (op, ch) in callback() } obs.append(xa) let xb = self.observe(\CirclePolygon.origin.x, options: [.new]) { (op, ch) in callback() } obs.append(xb)Funciona, pero no me gusta la duplicación. He intentado pasar una lista de KeyPaths:
obs = [] let keyPaths = [\CirclePolygon.radius, \CirclePolygon.origin.x] for keyPath in keyPaths { let xa = self.observe(keyPath, options: [.new]) { (op, ch) in callback() } obs.append(xa) } Pero aparece el error del compilador: Generic parameter 'Value' could not be inferred
¿Hay alguna forma de hacer esto?
En primer lugar, recuerde que KVO solo funciona en instancias de NSObject (o una subclase). Entonces, si la propiedad de origin de CirclePolygon es un CGPoint o alguna otra struct , obtendrá un error de tiempo de ejecución cuando intente registrar un observador de \CirclePolygon.origin.x , porque KVO no puede aplicarse a la propiedad x de un CGPoint .
En segundo lugar, las rutas clave que proporcionó tienen diferentes tipos. Digamos (para abordar mi primer punto) que desea observar el radius (un Double ) y el origin (un CGPoint ). Las rutas clave tienen diferentes tipos:
\CirclePolygon.radius // type: KeyPath<CirclePolygon, Double> \CirclePolygon.origin // type: KeyPath<CirclePolygon, CGPoint>Si intenta colocarlos en una sola matriz, sin especificar explícitamente el tipo de matriz, Swift lo deducirá de la siguiente manera:
let keyPaths = [\CirclePolygon.origin, \CirclePolygon.radius] // deduced type of keyPaths: [PartialKeyPath<CirclePolygon>] Deduce [PartialKeyPath<CirclePolygon>] porque PartialKeyPath<CirclePolygon> es el ancestro común más cercano de KeyPath<CirclePolygon, Double> y KeyPath<CirclePolygon, CGPoint> .
Por lo tanto, en el ciclo for en el que itera sobre la matriz, está pasando un objeto con tipo estático PartialKeyPath<CirclePolygon> como el argumento keyPath del método observe(_:options:changeHandler:) . Desafortunadamente, ese método se declara de la siguiente manera :
public func observe<Value>( _ keyPath: KeyPath<Self, Value>, options: NSKeyValueObservingOptions = [], changeHandler: @escaping (Self, NSKeyValueObservedChange<Value>) -> Void) -> NSKeyValueObservation
Es decir, el método requiere un KeyPath<Self, Value> pero está pasando un PartialKeyPath<Self> , por lo que Swift emite un error. Como suele ocurrir con los errores de Swift relacionados con problemas de tipo genérico, el error no es muy útil.
No puede resolver este problema con la conversión, porque no puede (por ejemplo) convertir KeyPath<CirclePolygon, Double> a KeyPath<CirclePolygon, Any> .
Una solución puede ser usar una función local para encapsular el código común, como esta:
func register(callback: @escaping () -> ()) -> [NSKeyValueObservation] { func r<Value>(_ keyPath: KeyPath<CirclePolygon, Value>) -> NSKeyValueObservation { return observe(keyPath, options: []) { (_, _) in callback() } } return [ r(\.radius), r(\.origin), ] }