1

I am trying to observe value changes of an NSMutableArray in an object written in ObjectiveC. The observer however is an UIViewController written in Swift. I'm not able to get the KVO notifications. These are my changes:

@interface Record : BaseObject
@property (nonatomic) NSMutableArray *commentsArray;


-(NSUInteger) countOfCommentsArray {
    return _commentsArray.count;
}
-(id) objectInCommentsArrayAtIndex:(NSUInteger)index {
    return [_commentsArray objectAtIndex:index];
}
-(void) insertObject:(Comment *)comment inCommentsAtIndex:(NSUInteger)index {
    [_commentsArray insertObject:comment atIndex:index];
}

-(void) insertComments:(NSArray *)array atIndexes:(NSIndexSet*)indexes {
    [_commentsArray insertObjects:array atIndexes:indexes];
}

-(void) removeCommentsAtIndexes:(NSIndexSet *)indexes {
    [_commentsArray removeObjectsAtIndexes:indexes];
}

-(void) removeObjectFromCommentsAtIndex:(NSUInteger)index {
    [_commentsArray removeObjectAtIndex:index];
}
-(void) replaceObjectInCommentsAtIndex:(NSUInteger)index withObject:(id)object {
    [_commentsArray replaceObjectAtIndex:index withObject:object];
}

The BaseObject derives from NSObject.

In my Observer class which is a Viewcontroller, and written in Swift I have the following.

In one of setup methods.

  myInspectionRecord?.addObserver(self, forKeyPath: "commentsArray", options: NSKeyValueObservingOptions.New, context: nil);

And I have the method overrriden.

  override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        print("Value Changed");
    }

But somehow when I add a new object to the array using the API itself, the observeValue method doesnt seem to be triggered. Any pointers on what I might be missing here? Or doesn't KVO work between Swift and Objective C files?

Any help would be appreciated. Thanks!

Tejas HS
  • 2,565
  • 2
  • 16
  • 13
  • Related: http://stackoverflow.com/questions/24092285/is-key-value-observation-kvo-available-in-swift – kennytm Dec 28 '15 at 18:48
  • Is `myInspectionRecord` actually pointing to the final object you want to observe by the time that code is run? In other words: put a breakpoint on the `addObserver` line and see whether `myInspectionRecord` is `nil`. If you're assigning to that variable, make sure to always un-register and re-register before setting it. – DarkDust Dec 28 '15 at 18:57
  • BTW, since your Swift class derives from `UIViewController` (and thus from `NSObject`), KVO works just fine with it. – DarkDust Dec 28 '15 at 18:58
  • Yes. I checked that. `myInspectionRecord` is not nil. I am registering and unregistering everytime the variable is being set either. I'm wondering if I have missed any steps. – Tejas HS Dec 28 '15 at 19:05

1 Answers1

0

You've implemented the to-many key-value coding (!) methods according to Apple's KVC guide, but the method names are wrong.

Since your property is named commentsArray, all of the methods need to include that full name. For example, your insertObject:inCommentsAtIndex: should be named insertObject:inCommentsArrayAtIndex:.

According to this answer this should then give you the KVO calls for free, but you have to actually call them: the compiler seems to recognize that it needs to inject the required KVO calls for these methods.

The problem right now with your code is, neither of your methods alter the property and even if the compiler does inject KVO code, it would do so for the wrong key path (comments instead of commentsArray). There are two solutions: it seems using those KVC-methods give you KVO for free but I've never used this magic, so I don't know from experience whether it will work. It should, though.

There is a "brute-force" way that you could use if you want to modify the array in a completely different method: call the KVO methods willChangeValueForKey: and didChangeValueForKey: or even better, willChange:valuesAtIndexes:forKey: and didChange:valuesAtIndexes:forKey: yourself, like this:

[self willChangeValueForKey:NSStringFromSelector(@selector(commentsArray))];
// Modify commentsArray
[self didChangeValueForKey:NSStringFromSelector(@selector(commentsArray))];

// or better, for an insertion:

NSIndexSet *set = [NSIndexSet indexSetWithIndex:someIndex];
NSString *key = NSStringFromSelector(@selector(commentsArray));
[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:set forKey:key];
[_commentsArray insertObject:someObject atIndex:someIndex];
[self didChange:NSKeyValueChangeInsertion valuesAtIndexes:set forKey:key];

(I like to use NSStringFromSelector(@selector(commentsArray)) instead of @"commentsArray" since the compiler then knows it's a selector and refactoring will then warn you when you rename the property, for example.)

Community
  • 1
  • 1
DarkDust
  • 90,870
  • 19
  • 190
  • 224
  • The property itself doesn't have to change -- as long as the array is changed via those specifically named methods, you'll get change notifications for inserts / deletes on that array. You're right about the naming though, that's the main problem. Nice spot. I think if the OP fixes them to be like `insertObject:inCommentsArrayAtIndex:` then the KVO notifications will work. – Ewan Mellor Dec 28 '15 at 23:05
  • Thanks. That helped. The variable was named `comments` earlier. Guess thats the reason Xcode suggested the method. Fixed the method names and it worked. – Tejas HS Dec 29 '15 at 06:59