检测对NSManagedObject的特定属性的更改

时间:2010-10-10 16:50:45

标签: iphone core-data notifications nsmanagedobject

如何检测NSManagedObject的特定属性的更改?在我的核心数据数据模型中,我有一个代表待售产品的Product实体。 Product实体有几个属性:priceskuweightnumberInStock等。每当price属性{{1}更改,我需要执行一个冗长的计算。因此,我想知道任何 Product的{​​{1}}属性何时更改, [编辑],即使该更改来自合并另一个线程上保存的上下文即可。这样做的好方法是什么?我店里有成千上万的{{​​1}}个物品;显然,向每个人发送price消息是不可行的。

我一直在使用Product来检测更改,但它只通知我 托管对象已更改,而该对象的哪个属性已更改。每当任何更改为Product时,我都可以重做计算,但只要不相关的属性发生更改,就会导致无用的重新计算。我正在考虑制作addObserver实体(仅包含NSManagedObjectContextObjectsDidChangeNotification属性)并使用ProductPrice之间的一对一关系。这样,我可以检测price个对象的更改,以便开始计算。这对我来说似乎过于愚蠢。还有更好的方法吗?

更新

@railwayparade指出我可以使用Product的{​​{1}}方法来确定每个更新对象的哪些属性已更改。我完全错过了这个方法,如果没有在后台线程上进行更改并将其合并到主线程的上下文中,它将完全解决我的问题。 (见下一段。)

我完全错过了关于Price工作方式的微妙之处。据我所知,当另一个线程上保存的托管对象上下文合并到主线程上的上下文中时(使用Price),生成的changedValues 包含有关当前位于主线程的托管对象上下文中的对象的更改信息。如果更改的对象不在主线程的上下文中,则它不会成为通知的一部分。这很有道理,但不是我所期待的。因此,为了获得更详细的更改信息,我想使用一对一关系而不是属性实际上需要检查后台线程的NSManagedObject,而不是主线程的NSManagedObjectContextObjectsDidChangeNotification。当然,简单地使用mergeChangesFromContextDidSaveNotification:的{​​{1}}方法会更加明智,正如@railwayparade所指出的那样。但是,我仍然存在这样的问题:主线程上合并的更改通知不一定包含后台线程上的所有更改。

5 个答案:

答案 0 :(得分:41)

关于这个帖子的一点,

  

Core Data生成的NSManagedObjectContextObjectsDidChangeNotification指示托管对象已更改,但未指示哪个属性已更改。

实际上它确实如此。 “changedValues”方法可用于查询更改的属性。

类似的东西,

 if([updatedObjects containsKindOfClass:[Config class]]){
    //if the config.timeInterval changed
    NSManagedObject *obj = [updatedObjects anyObject];
    NSDictionary *dict=[obj changedValues];
    NSLog(@"%@",dict);
    if([dict objectForKey:@"timeInterval"]!=nil){
      [self renderTimers];
    }
  }

答案 1 :(得分:10)

这种情况是您需要自定义NSManagedObject子类的地方。您需要子类,因为您要向管理对象添加对价格更改做出反应的行为。

在这种情况下,您将覆盖price属性的访问者。使用数据模型编辑器中的弹出菜单创建自定义子类。然后选择price属性并选择“将Obj-C 2.0实现复制到剪贴板”。它会给你很多东西,但关键位看起来像这样:

- (void)setPrice:(NSNumber *)value 
{
    [self willChangeValueForKey:@"price"];
    [self setPrimitivePrice:value];
    [self didChangeValueForKey:@"price"];
}

只需添加代码即可处理价格变动,您就完成了。只要特定产品的价格发生变化,代码就会运行。

答案 2 :(得分:3)

你可以看看KVO(Key Value Observing)。不确定Core Data API中是否内置了包装器,但我知道它是Objective-C的一部分。

答案 3 :(得分:1)

我想我会在这里记录我的设计决定,以防它们对其他人有用。我的最终解决方案是基于TechZen的回答。

首先,我将从一个简短的,希望更清楚的问题开始重述:

在我的应用程序中,我想检测托管对象(price)的特定属性(Product)的更改。此外,我想知道这些更改是在主要线程还是后台线程上进行的。最后,即使主线程当前在其托管对象上下文中没有更改的Product对象,我也想了解这些更改。

核心数据生成的NSManagedObjectContextObjectsDidChangeNotification表示 托管对象已更改,但未指示 属性已更改。我的kludgy解决方案是创建一个包含单个Price属性的price托管对象,并将price中的Product属性替换为与{Price的一对一关系1}}托管对象。现在,只要对Price托管对象进行了更改,核心数据NSManagedObjectContextObjectsDidChangeNotification就会在其Price集中包含该NSUpdatedObjectsKey个对象。我只需要将这些信息提供给主线程。这一切听起来不错,但有一个障碍。

我的核心数据存储正由两个线程操纵。这是以“通常”的方式完成的 - 每个线程都有一个托管对象上下文和一个共享持久存储协调器。后台线程进行更改后,将保存其上下文。主线程通过NSManagedObjectContextDidSaveNotification检测上下文保存,并使用mergeChangesFromContextDidSaveNotification:合并上下文更改。 (实际上,由于通知是在他们发布的同一个线程中收到的,因此在后台线程上收到NSManagedObjectContextDidSaveNotification并通过performSelectorOnMainThread:传递给主线程进行合并。)由于合并后,Core Data会生成一个NSManagedObjectContextObjectsDidChangeNotification,表示已更改的对象。但是,据我所知,NSManagedObjectContextObjectsDidChangeNotification仅包括当前在接收上下文中表示的那些对象。从更新UI的角度来看,这是有道理的。如果未显示托管对象,则可能不会在上下文中,因此无需将其包含在通知中。

在我的情况下,我的主线程需要知道对托管对象所做的更改,无论它们当前是否在主线程的上下文中。如果任何价格发生变化,主线程需要对操作进行排队以处理该价格变化。因此,主线程需要知道所有价格变化,即使这些更改是在后台线程上对当前未在主线程上访问的产品进行的。显然,由于NSManagedObjectContextObjectsDidChangeNotification仅包含有关当前主线程上下文中对象的信息,因此无法满足我的需求。

我想到的第二个选项是在后台线程保存其上下文时使用后台线程生成的NSManagedObjectContextDidSaveNotification。此通知包含有关对托管对象的所有更改的信息。我已经检测到这个通知并将其传递给主线程以进行合并,那么为什么不查看内部并查看所有已更改的托管对象?您会记得托管对象并不是要跨线程共享。因此,如果我开始检查主线程上NSManagedObjectContextDidSaveNotification的内容,我会崩溃。嗯......那么mergeChangesFromContextDidSaveNotification:怎么做呢?显然,mergeChangesFromContextDidSaveNotification:专门用于解决“不跨线程共享托管对象”的限制。

我想到的第三个选项是在后台线程和上注册NSManagedObjectContextDidSaveNotification,同时仍在后台线程上将其内容转换为包含对象ID的特殊PriceChangeNotification而不是托管对象。在主线程上,我可以将对象ID转换回托管对象。这种方法仍然需要一对一Price关系,以便价格变化反映为对Price托管对象的更改。

我根据TechZen的建议提出第四个选项,以覆盖Product托管对象中的价格设定器。我没有使用一对一的关系来强制Core Data生成我需要的通知,而是回到使用price属性。在我的setPrice方法中,我发布了自定义PriceChangeNotification。此通知在后台线程上接收,用于构造一组价格更改为Product的对象。后台线程保存其上下文后,会发布自定义PricesDidChangeNotification,其中包含价格已更改的所有Product个对象的对象ID。此通知可以安全地传输到主线程并进行检查,因为它使用对象ID而不是托管对象本身。在主线程上,我可以获取这些对象ID引用的Product对象,并对一个操作进行排队,以便在新的后台线程上执行冗长的“价格变化”计算。

答案 4 :(得分:0)

您使用的是NSArrayController还是其他控制器?据推测,您需要某种方式让用户与模型进行交互。正是这种交互点为这种类型的更新调用提供了一个很好的钩子。也许适当的策略是观察阵列控制器arrangedObjects的相关属性。