I'm interested in registering my view controller for KVO notifications on a model object's properties.
The "member" property of the view controller is an NSManagedObject subclass and uses Core Data's provided accessor methods (via @dynamic
). It has four properties: firstName, lastName, nickname, and bio, which are all NSStrings.
Here's the registering and unregistering for KVO:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.member addObserver:self
forKeyPath:@"firstName"
options:NSKeyValueObservingOptionNew
context:kFHMemberDetailContext];
[self.member addObserver:self
forKeyPath:@"lastName"
options:NSKeyValueObservingOptionNew
context:kMemberDetailContext];
[self.member addObserver:self
forKeyPath:@"nickname"
options:NSKeyValueObservingOptionNew
context:kMemberDetailContext];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.member removeObserver:self forKeyPath:@"firstName" context:kMemberDetailContext];
[self.member removeObserver:self forKeyPath:@"lastName" context:kMemberDetailContext];
[self.member removeObserver:self forKeyPath:@"nickname" context:kMemberDetailContext];
}
Implementation of callback method
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context != kFHMemberDetailContext) {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
return;
}
self.kvoCount++;
if ([keyPath isEqualToString:@"firstName"]) {
NSLog(@"firstName KVO'd");
}
else if ([keyPath isEqualToString:@"lastName"]) {
NSLog(@"lastName KVO'd");
}
else if ([keyPath isEqualToString:@"nickname"]) {
NSLog(@"nickname KVO'd");
}
}
When I drive this code from a unit test, I receive three notifications when I modify the "bio" property, and four notifications when modifying firstName, lastName, or nickname. That's consistently three too many notifications!
It seems to be something simple that I've done wrong, but I can't figure out what's causing the extraneous notifications. If I po the change dictionary, the NSKeyValueChangeKindKey is always NSKeyValueChangeSetting, but for the unwanted notifications the NSKeyValueChangeNewKey is NULL.
The test driving this bit of code:
- (void)setUp
{
NSManagedObjectModel *mom = [NSManagedObjectModel mergedModelFromBundles:@[[NSBundle mainBundle]]];
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
[psc addPersistentStoreWithType:NSInMemoryStoreType
configuration:nil
URL:nil
options:nil
error:NULL];
NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[managedObjectContext setPersistentStoreCoordinator:psc];
member = [NSEntityDescription insertNewObjectForEntityForName:@"Member" inManagedObjectContext:managedObjectContext];
UIStoryboard *sb = [UIStoryboard storyboardWithName:@"MainStoryboard_iPhone" bundle:[NSBundle mainBundle]];
memberDetailVC = [sb instantiateViewControllerWithIdentifier:kFHMemberDetailTableViewControllerIdentifier];
[memberDetailVC setMember:member];
}
- (void)tearDown
{
[memberDetailVC viewWillDisappear:NO];
}
- (void)testChangesToMemberFirstNamePropertyCausesKVO
{
[memberDetailVC viewWillAppear:NO];
[member setFirstName:@"Unit Test"];
STAssertTrue(memberDetailVC.kvoCount, (NSInteger)1, @"View controller should have received a single KVO notification");
}
Like I said, this fails having received 4 notifications (one for each property with a new value of null, and then finally the expected notification).