Key-Value Observe in Swift without displaying insert and delete in arrays

I created a class containing an array. I added an observer to this array in the view controller and made some modifications to this array.

The problem is that when I print the change dictionary returned by registerValueForKeyPath (), I can only see changes like NSKeyValueChangeSetting. In other words, the method tells me that the array has changed, provides me with old and new arrays (containing all the elements), but I would like to get information about which specific elements were added or removed.

Here is a sample 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 in 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 )] 

In this example, the change dictionary does not show every new element, because it is added to the array using the change view NSKeyValueChangeInsertion?

+7
arrays ios swift key-value-observing
source share
1 answer

According to the Swift guide:

For arrays, copying is only performed when an action is performed that can change the length of the array. This includes adding, inserting, or deleting elements or using a range index to replace the range of elements in an array.

Take the append operation as an example. Under the hood, when you join your array, Swift creates a new array in memory, copies the elements from the existing animals array to this new array - plus a new element - then assigns this new array to the animals variable. This dexterity is why you only ever get Type 1 ( Setting ), because in fact, every β€œedit” actually leads to the creation of a new array.

This differs from NSMutableArray because the behavior is a little intuitive - changes are made to the existing array (there is no capital copy to the new array), therefore the array that exists after editing is the same array that existed before it, therefore the value stored in the dictionary change with the key NSKeyValueChangeKindKey , can be one of .Insertion , .Removal , etc.

Even this is not the whole story, because the way you make changes consistent with KVO in NSMutableArray depends on whether you use Swift or Objective-C. In Objective-C, Apple strongly recommends implementing what they call optional mutable indexed access. These are just standard-signed methods that make KVO-compatible 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 does not seem to be there, so the only way to make KVO-compatible changes is through a modified proxy object described on the NSKeyValueCoding page. Here is 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 } } 
+11
source share

All Articles