在validateForInsert中执行获取请求过于昂贵

我最近在我的核心数据模型中做了一个重构传递,我从这里使用多层pipe理对象上下文模型: http : //www.cocoanetics.com/2012/07/multi-context-coredata/ 。

我已经成功地隔离了所有的核心数据parsing,以便新的托pipe对象被parsing并插入到后台线程的子MOC中,并且这些更改最终会批量保存到父/主MOC,然后写入持续的商店协调员通过其父母/作家MOC。

这已经稍微改善了我的UI响应能力,因为之前的大批量写操作是在父/主MOC上完成的,并locking了UI线程。

我想进一步改进我们的对象插入和validation。 每当应用程序打开,并在一个有点规律的时间间隔内,有一个configuration文件请求,在这个请求期间,有数十个或数百个对象以新的值发送。 我已经select为所有这些对象简单地创buildNSManagedObjects ,将它们插入到子MOC中,并允许validation来处理删除重复项。

我的问题是,是否每次调用validateForInsert :对于NSManagedObject NSFetchRequest ,执行NSFetchRequest都很昂贵。 我已经看到了几个似乎使用这种模式的StackOverflow的答案,例如: https : //stackoverflow.com/a/2245699/2184893 。 我想在创build实体之前执行这个操作,而不是进行validation,因为如果两个线程同时创build同一个对象,则两者都将被创build,并且validation必须在父线程的插入/合并时发生。

那么,使用这种方法是否昂贵? 这是常见的做法吗? 另外,使用validateForInsertvalidate是否有区别?

 -(BOOL)validateUniqueField:(id *)ioValue error:(NSError * __autoreleasing *)outError{ // The property being validated must not already exist NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([self class])]; fetchRequest.predicate = [NSPredicate predicateWithFormat:@"uniqueField == %@", *ioValue]; int count = [self.managedObjectContext countForFetchRequest:fetchRequest error:nil]; if (count > 0) { if (outError != NULL) { NSString *errorString = NSLocalizedString( @"Object must have unique value for property", @"validation: nonunique property"); NSDictionary *userInfoDict = @{ NSLocalizedDescriptionKey : errorString }; *outError = [[NSError alloc] initWithDomain:nil code:0 userInfo:userInfoDict]; } return NO; } return YES; } 

我用例可能会创build同一个对象的多个线程,例如,如果我asynchronous地请求一个区域内的所有用户,这些区域中的两个重叠,并在同一时间给我相同的用户对象,每个线程试图创build同一个用户在自己的context. findOrCreate context. findOrCreate将无法validation该对象是否已经在另一个线程/上下文中创build。 目前我通过在validateForInsert进行检查来处理这个问题。

在validation方法里面提取?

你的问题是聪明的,它隐藏了几个问题!

那么,使用这种方法是否昂贵?

这可能是非常昂贵的,因为您至less要对每个要validation的对象进行提取(作为保存的一部分进行validation)。

这是常见的做法吗?

我真的希望不会! 我以前只看过一次,结果不好(继续阅读)。

另外,使用validateForInsert和validation是否有区别?

我不确定你在这里的意思。 托pipe对象具有以下validation方法: validateForInsertvalidateForUpdatevalidateForDelete 。 每个执行它自己的规则以及调用validateValue:forKey:error:对于单独的属性,这反过来将调用模式的任何实现validate<Key>:error: 例如, validateForInsert将在调用其他validation方法(例如,在模型编辑器中标记一个build模属性非可选属于插入validation规则)之前,执行在托pipe对象模型中定义的任何插入validation规则。 在上下文保存时自动调用validation,您可以随时调用它。 如果您想显示用户必须纠正保存才能完成的错误,这可能很有用。

这就是说,阅读一个解决scheme,你似乎试图解决的问题。

关于在validation方法中获取…

访问validation方法内的对象图是不明智的。 在执行提取时,您正在更改上下文中的对象图 – 访问对象,触发错误等。validation在保存过程中自动进行,并在此时更改内存中对象图 – 即使您不是直接改变财产价值 – 可能会有一些戏剧性的,很难预测的副作用。 这不会是快乐的时光。

唯一性的正确解决scheme:查找或创build

您似乎试图确保托pipe对象是唯一的。 核心数据没有为此提供内build的机制,但它有一个推荐的模式来实现:“查找或创build”。 这是在访问对象时完成的,而不是在validation或保存对象时完成的。

确定是什么使这个实体独特。 这可能是一个单一的属性值(在你的情况下,它似乎是一个单一的属性),或几个组合(例如“firstName”和“lastName”在一起是什么使“人”是唯一的)。 基于该唯一性标准,您可以查询现有对象匹配的上下文。 如果find匹配,则返回它们,否则使用这些值创build一个对象。

这是一个基于你的问题代码的例子。 这将使用“uniqueField”的值作为唯一性标准,显然如果你有多个属性一起使你的实体变得更加复杂一点。

例:

 // I am using NSValue here, as your example doesn't indicate a type. + (void) findOrCreateWithUniqueValue:(NSValue *)value inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext completion:(void (^)(NSArray *results, NSError *error))completion { [managedObjectContext performBlock:^{ NSError *error = nil; NSEntityDescription *entity = [NSEntityDescription entityForName:NSStringFromClass(self) inManagedObjectContext:managedObjectContext]; NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; fetchRequest.entity = entity; fetchRequest.predicate = [NSPredicate predicateWithFormat:@"uniqueField == %@", value]; NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error]; if ([results count] == 0){ // No matches found, create a new object NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:managedObjectContext]; object.uniqueField = value; results = [NSArray arrayWithObject:object]; } completion(results, error); }]; } 

这将成为您获取对象的主要方法。 在你的问题中描述的场景中,你需要定期从某些源代码获取必须应用于pipe理对象的数据。 使用上述方法,该过程将看起来像….

 [MyEntityClass findOrCreateWithUniqueValue:value completion:^(NSArray *results, NSError *error){ if ([results count] > 0){ for (NSManagedObject *object in results){ // Set your new values. object.someValue = newValue; } } else { // No results, check the error and handle here! } }]; 

这可以高效,高效地完成,并具有适当的数据完整性。 你可以在抓取实现中使用批处理错误等,如果你愿意采取内存命中。 一旦对所有传入数据执行了上述操作,就可以保存上下文,并且将对象和它们的值有效地推送到父商店。

这是使用Core Data实现唯一性的首选方法。 在“ 核心数据编程指南”中非常简要地介绍了这一点,并间接提到了这一点

为了扩大这一点…必须做“批量”查找或创build并不罕见。 在您的场景中,您将获得需要应用于托pipe对象的更新列表,如果它们不存在,则创build新对象。 显然,上面的示例查找或创build方法可以做到这一点,但你也可以更有效地做到这一点。

核心数据有“批次错误”的概念。 如果您知道要使用多个对象,则可以一次对所有对象单独进行批处理,而不是单独对每个对象进行访问。 这意味着更less的磁盘访问和更好的性能。

批量查找或创build方法可以利用这一点。 请注意,因为所有这些对象现在都会有“被解雇”的错误,所以这将会使用更多的内存 – 但不会超过每次调用上面的单个查找或创build。

我不会重复所有以前的方法,而是将其解释为:

  // 'values' is a collection of your unique identifiers. + (void) findOrCreateWithUniqueValues:(id <NSFastEnumeration>)values inManagedObjectContext:(NSManagedObjectContext *)managedObjectContext completion:(void (^)(NSArray *results, NSError *error))completion { ... // Effective use of IN will ensure a batch fault fetchRequest.predicate = [NSPredicate predicateWithFormat:@"SELF.uniqueField IN %@", values]; // returnsObjectsAsFaults works inconsistently across versions. fetchRequest.returnsObjectsAsFaults = NO; ... NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error]; // uniqueField values we initially wanted NSSet *wanted = [NSSet setWithArray:values]; // uniqueField values we got from the fetch NSMutableSet *got = [NSMutableSet setWithArray:[results valueForKeyPath:@"uniqueField"]]; // uniqueField values we will need to create, the different between want and got NSMutableSet *need = nil; if ([got count]> 0){ need = [NSMutableSet setWithSet:wanted]; [need minusSet:got]; } NSMutableSet *resultSet = [NSMutableSet setWithArray:fetchedResults]; // At this point, walk the values in need, insert new objects and set uniqueField values, add to resultSet ... // And then pass [resultSet allObjects] to the completion block. } 

批处理错误的有效使用对于任何一次处理多个对象的应用程序来说都是一个巨大的推动。 像往常一样,configuration文件。 不幸的是,不同核心数据版本之间的断层行为有很大差异。 在旧版本中,使用托pipe对象ID的附加提取更为有利。 你的旅费可能会改变。

与单个调用比较一组标识符相比,对DB的单独调用是昂贵的。 在比较单个值时,可以使用集合或数组上的in运算符与一组值进行比较。 因此,把这个批次取下来,用-valueForKey:提取id,然后重写上面的代码来接受一个值的数组。

我认为可以在validation方法中进行提取,例如validateForInsert 。 实际上,如果所需的提取失败,则可以将错误传递回上下文保存。 只要确保将错误param传递给您的提取,并返回false如果获取nil结果。