Key-Value Observe que Swift no muestra inserciones y eliminaciones en matrices

Creé una class que contiene una matriz. Agregué un observador a esa matriz en un controller de vista y realicé algunas modificaciones a esa matriz.

El problema es que cuando imprimo el dictionary de cambio devuelto por el método observeValueForKeyPath () solo puedo ver cambios de tipo NSKeyValueChangeSetting. En otras palabras, el método me dice que la matriz ha cambiado, me proporciona las matrices viejas y nuevas (que contienen todos los elementos), pero me gustaría recibir la información de qué elementos específicos se agregaron o eliminaron.

Aquí hay un código de ejemplo.

Esta es la class cuya matriz será observada.

private let _observedClass = ObservedClass() class ObservedClass: NSObject { dynamic var animals = [String]() dynamic var cars = [String]() class var shanetworkingInstance: ObservedClass { return _observedClass } } 

Y este es el código en mi controller de vista.

 class ViewController: UIViewController { var observedClass = ObservedClass.shanetworkingInstance requinetworking init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) observedClass.addObserver(self, forKeyPath: "animals", options: .New | .Old, context: nil) } deinit { observedClass.removeObserver(self, forKeyPath: "animals") } override func viewDidLoad() { super.viewDidLoad() observedClass.animals.insert("monkey", atIndex: 0) observedClass.animals.append("tiger") observedClass.animals.append("lion") observedClass.animals.removeAtIndex(0) } override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) { println(change) } } 

Cuando ejecuto el código anterior, obtengo este resultado en la console:

 [kind: 1, old: ( ), new: ( monkey )] [kind: 1, old: ( monkey ), new: ( monkey, tiger )] [kind: 1, old: ( monkey, tiger ), new: ( monkey, tiger, lion )] [kind: 1, old: ( monkey, tiger, lion ), new: ( tiger, lion )] 

¿No debería, en este ejemplo, el dictionary de cambio mostrar cada elemento nuevo a medida que se agrega a la matriz, utilizando el tipo de cambio NSKeyValueChangeInsertion?

De acuerdo con la guía Swift:

Para las matrices, la copy solo se lleva a cabo cuando realiza una acción que tiene el potencial de modificar la longitud de la matriz. Esto incluye anexar, insert o eliminar elementos, o usar un subíndice a distancia para replace un range de elementos en la matriz.

Tomemos como ejemplo una operación de append . Debajo del capó, al agregar a su matriz, Swift crea una nueva matriz en la memory, copy los elementos de la matriz de animals existente en esta nueva matriz, más el nuevo elemento, y luego asigna esta nueva matriz a la variable de animals . Este juego de manos es la razón por la que solo obtienes el tipo de 1 ( Setting ), porque de hecho cada ' edición ' realmente da como resultado una nueva matriz que se está creando.

Es diferente con NSMutableArray porque el comportamiento es un poco más intuitivo: las ediciones se realizan en la matriz existente (no hay copy detrás de las escenas en una nueva matriz), por lo que la matriz que existe después de la edición es la misma matriz que existía antes – de ahí el valor almacenado en el dictionary de change con la key NSKeyValueChangeKindKey puede ser uno de .Insertion , .Removal , etc.

Sin embargo, esa no es toda la historia, ya que la forma en que realiza los cambios que cumplen con NSMutableArray un NSMutableArray varía según si usa Swift u Objective-C. En Objective-C, Apple recomienda encarecidamente implementar lo que ellos llaman los accesores indexados mutables opcionales. Estos son solo methods con una firma estándar que hacen que los cambios que cumplen con KVO sean muy eficientes.

 // Mutable Accessors /////////////////////////////////////////// // This class has a property called <children> of type NSMutableArray // MUST have this (or other insert accessor) -(void)insertObject:(NSNumber *)object inChildrenAtIndex:(NSUInteger)index { [self.children insertObject:object atIndex:index]; } // MUST have this (or other remove accessor) -(void)removeObjectFromChildrenAtIndex:(NSUInteger)index { [self.children removeObjectAtIndex:index]; } // OPTIONAL - but a good idea. -(void)replaceObjectInChildrenAtIndex:(NSUInteger)index withObject:(id)object { [self.children replaceObjectAtIndex:index withObject:object]; } 

Swift no parece tener estos, por lo que la única forma de hacer cambios compatibles con KVO es a través del object proxy mutable, descrito en la página NSKeyValueCoding . Aquí hay un ejemplo muy rápido:

 @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate { dynamic var children: NSMutableArray = NSMutableArray() //////////////////////////////////// func applicationDidFinishLaunching(aNotification: NSNotification) { addObserver(self, forKeyPath: "children", options: .New | .Old, context: &Update) // Get the KVO/KVC compatible array var childrenProxy = mutableArrayValueForKey("children") childrenProxy.addObject(NSNumber(integer: 20)) // .Insertion childrenProxy.addObject(NSNumber(integer: 30)) // .Insertion childrenProxy.removeObjectAtIndex(1) // .Removal } }