我最近在我的核心数据模型中进行了重构,并且正在从这里使用多层托管对象上下文模型:http://www.cocoanetics.com/2012/07/multi-context-coredata/。
我已经成功地隔离了所有核心数据解析,以便将新的托管对象解析并插入到后台线程的子MOC中,并且这些更改最终将批量保存到父/主MOC中,然后最终写入持久存储协调员通过其父/写者MOC。
由于以前在父/主MOC上进行了大批量写入并锁定了UI线程,因此这在某种程度上显着改善了我的UI响应能力。
我想进一步改善我们的对象插入和验证。每次打开应用程序时,都会以一定的规则间隔打开一个配置文件请求,在此期间,将使用新值向下发送数十或数百个对象。我选择只为所有这些对象创建NSManagedObjects ,将它们插入到子MOC中,并允许进行验证以消除重复项。
我的问题是,是否在每次对NSFetchRequest 的调用中都执行validateForInsert :对于NSManagedObject 来说代价很高。我在StackOverflow上看到了几个似乎正在使用此模式的答案,例如:https://stackoverflow.com/a/2245699/2184893。我要执行此操作而不是在创建实体之前进行验证,因为如果两个线程同时在同一时间创建同一对象,则会同时创建两个对象,并且验证必须在父线程的插入/合并时进行。
那么,使用这种方法是否昂贵?这是惯例吗?另外,使用validateForInsert 和validate 有区别吗?
-(BOOL)validateUniqueFieldid *)ioValue errorNSError * __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;
}
例如,如果我异步请求一个区域中的所有用户,这些区域中的两个重叠并且大约在同一时间给我相同的用户对象,那么我可能会使用多个线程来创建同一对象的用例使用自己的context. findOrCreate 的同一用户将无法验证该对象是否已在其他线程/上下文中创建。目前,我正在通过检查validateForInsert 来处理此问题。
Best Answer-推荐答案 strong>
正在获取验证方法? 您的问题很聪明,因为它隐藏了几个问题! 那么,使用这种方法是否昂贵? 这可能非常昂贵,因为在保存过程中,您至少要对要验证的每个对象进行提取(在保存过程中会自动调用验证)。 这是惯例吗? 我真的希望不要!我以前只看过一次,结果却不好(继续阅读)。 另外,使用validateForInsert和validate是否有区别? 我不确定您在这里的意思。受管对象具有以下验证方法:validateForInsert ,validateForUpdate 和validateForDelete 。它们每个都执行自己的规则,并为各个属性调用validateValue:forKey:error: ,依次调用validate<Key>:error: 模式的任何实现。例如,validateForInsert 将在调用其他验证方法之前执行托管对象模型中定义的任何插入验证规则(例如,将模型属性在模型编辑器中标记为非可选是插入验证规则)。 保存上下文时会自动调用验证,但是您可以随时调用它。如果您想显示必须纠正以完成保存的用户错误等,这将很有用。 也就是说,请继续阅读以获取您似乎要解决的问题的解决方案。 关于在验证方法中获取... 在验证方法中访问对象图是不明智的。执行提取时,您正在该上下文中更改对象图-访问对象,引发错误等。保存过程中会自动进行验证,并且此时会更改内存中的对象图-即使您没有直接更改属性值-可能会有一些戏剧性且难以预测的副作用。这不是快乐的时光。 唯一性的正确解决方案:查找或创建 您似乎在尝试确保托管对象是唯一的。核心数据没有为此提供内置机制,但是有一种建议的实现方式:“查找或创建”。这是在访问对象时完成的,而不是在验证或保存对象时完成的。 确定是什么使该实体唯一。这可以是单个属性值(在您的情况下,它似乎是单个属性),也可以是多个属性的组合(例如,“firstName”和“lastName”一起使“person”具有唯一性)。基于该唯一性条件,您可以在上下文中查询现有的对象匹配项。如果找到匹配项,则返回它们,否则使用这些值创建一个对象。 这是一个基于您问题代码的示例。这将使用“uniqueField”的值作为唯一性标准,显然,如果您拥有多个使您的实体唯一的属性,这将变得更加复杂。 例: // I am using NSValue here, as your example doesn't indicate a type.
+ (void) findOrCreateWithUniqueValueNSValue *)value inManagedObjectContextNSManagedObjectContext *)managedObjectContext completionvoid (^)(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 arrayWithObjectbject];
}
completion(results, error);
}];
}
这将成为您获取对象的主要方法。在问题描述的场景中,您会定期从必须应用于托管对象的某个来源获取数据。使用以上方法,该过程将类似于...。[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实现唯一性的首选方法。这是在Core Data Programming Guide中非常简短地提到的。 对此进行扩展... 必须进行“批量”查找或创建操作并不少见。在您的方案中,您将获取需要应用于托管对象的更新列表,如果不存在新对象,则创建它们。显然,上面的示例“查找或创建”方法可以做到这一点,但是您也可以更有效地做到这一点。 核心数据的概念是“批量故障”。如果您知道要使用多个对象,那么可以一次批处理所有对象,而不是在访问每个对象时分别对每个对象进行故障处理。这意味着更少的磁盘访问次数和更好的性能。 批量查找或创建方法可以利用此优势。请注意,由于所有这些对象现在都将“触发”故障,因此将使用更多的内存,但不会比在每个对象上调用上述单个查找或创建时要多。 我将重复以下所有方法,而不是重复所有前面的方法: // 'values' is a collection of your unique identifiers.
+ (void) findOrCreateWithUniqueValuesid <NSFastEnumeration>)values inManagedObjectContextNSManagedObjectContext *)managedObjectContext completionvoid (^)(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.
}
对于同时处理多个对象的任何应用程序而言,有效使用批处理故障可能会极大地促进其发展。与往常一样,用仪器进行轮廓分析。不幸的是,在不同的核心数据版本之间,故障行为已发生很大变化。在较早的发行版中,使用托管对象ID进行额外的获取更为有益。你的旅费可能会改变。
关于ios - 在validateForInsert中执行获取请求过于昂贵,我们在Stack Overflow上找到一个类似的问题:
https://stackoverflow.com/questions/25944579/
|