2

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

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

这是 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,最后是预期的通知)。

4

2 回答 2

2

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

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

这引发了所有其他问题,但我将把这些留到其他时间。现在看来我应该关掉电脑去喝一两杯威士忌……三杯。

于 2013-02-14T21:39:19.453 回答
0

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

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

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

于 2013-02-14T11:09:23.930 回答