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 that when I print the change dictionary returned by the observeValueForKeyPath() method I can only see changes of kind NSKeyValueChangeSetting. In other words, the method tells me the array has changed, provides me with the old and new arrays (containing all elements) but I would like to receive the information of which specific items were added or removed.
Here is some example code.
This is the class whose array will be observed.
private let _observedClass = ObservedClass()
class ObservedClass: NSObject {
dynamic var animals = [String]()
dynamic var cars = [String]()
class var sharedInstance: ObservedClass {
return _observedClass
}
}
And this is the code at my view controller.
class ViewController: UIViewController {
var observedClass = ObservedClass.sharedInstance
required 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)
}
}
When I run the above code, I get this result on the 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
)]
Shouldn't, on this example, the change dictionary show each new item as it is added to the array, using the change kind NSKeyValueChangeInsertion?
According to the Swift guide:
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 existinganimals
array into this new array - plus the new item - then assigns this new array to theanimals
variable. This sleight-of-hand is why you only ever get the kind of1
(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 thechange
dictionary with the keyNSKeyValueChangeKindKey
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.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: