I created a class which contains an array. I added an observer to that array in a view controller and performed some modifications to that array.
The problem is tha
According to the Swift guide:
For arrays, copying only takes place when you perform an action that has the potential to modify the length of the array. This includes appending, inserting, or removing items, or using a ranged subscript to replace a range of items in the array.
Take as an example an append
operation. Under the hood, when you append to your array, Swift creates a new array in memory, copies the items from the existing animals
array into this new array - plus the new item - then assigns this new array to the animals
variable. This sleight-of-hand is why you only ever get the kind of 1
(Setting
), because in fact each 'edit' actually results in a new array being created.
It's different with NSMutableArray
because the behaviour is a bit more intuitive - edits are made to the existing array (there's no behind-the-scenes copying to a new array), so the array that exists after the edit is the same array that existed before it - hence the value stored in the change
dictionary with the key NSKeyValueChangeKindKey
can be one of .Insertion
, .Removal
, etc.
Even that isn't the whole story however, because the way you make an KVO compliant changes to an NSMutableArray
varies depending on whether you're using Swift or Objective-C. In Objective-C, Apple strongly recommend implementing what they call the optional mutable indexed accessors. These are just methods with a standard signature that make KVO-compliant changes in a very efficient way.
// 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 doesn't appear to have these, so the only way to make KVO-compliant changes is via the mutable proxy object - described in the NSKeyValueCoding page. Here's a very quick example:
@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
}
}