KVO +托管对象 - 接收太多通知

时间:2013-02-14 04:42:10

标签: objective-c core-data key-value-observing

我有兴趣在模型对象的属性上注册我的视图控制器以获取KVO通知。

视图控制器的“member”属性是NSManagedObject子类,它使用Core Data提供的访问器方法(通过@dynamic)。它有四个属性:firstName,lastName,nickname和bio,它们都是NSStrings。

以下是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];
}

回调方法的实现

- (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");
    }
}

当我从单元测试中驱动此代码时,我在修改“bio”属性时收到三个通知,在修改firstName,lastName或昵称时收到四个通知。这通常是三个太多的通知!

我做错了似乎很简单,但我无法弄清楚是什么导致无关的通知。如果我更改字典,NSKeyValueChangeKindKey始终是NSKeyValueChangeSetting,但对于不需要的通知,NSKeyValueChangeNewKey为NULL。

推动这段代码的测试:

- (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");
}

就像我说的那样,收到了4个通知(每个属性一个,新值为null,最后是预期的通知)。

2 个答案:

答案 0 :(得分:2)

所以我的问题的答案是我的单元测试的问题。在我的setup方法中,我正在创建托管对象上下文,将托管对象插入其中,然后在我的-setUp方法结束时对上下文进行dealloc。

如果我在测试套件中将MOC作为ivar保留,则通知会按预期进行。

这提出了所有其他问题,但我会在其他时间留下这些问题。现在,似乎我应该关掉电脑,拿一两杯威士忌......或者三个。

答案 1 :(得分:0)

您正在此注册:kFHMemberDetailContext 您正在检查此上下文的不等式:kFHMemberDetailTableViewControllerContext 当然,这些都是常数,它们可能也是一样的。

因此,您可能总是会调用super实现。也许这就是导致你的额外通知的原因。

作为一个实用的解决方案,检查新密钥,如果它为空,则只需提前返回。