我正在与我的第一个CoreData应用程序作斗争,我遇到了几个问题 - 我觉得我做错了。
在主线程上,我有一个带有NSFetchedResultsController的UITableView,使用以下代码创建:
- (NSFetchedResultsController *)newFetchedResultsControllerWithSearch:(NSString *)searchString
{
NSSortDescriptor *sort = [[[NSSortDescriptor alloc] initWithKey:@"order" ascending:YES] autorelease];
NSArray* sortDescriptors = @[sort];
NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"contactId != 1"];
/*
Set up the fetched results controller.
*/
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
// Edit the entity name as appropriate.
NSEntityDescription *callEntity = [NSEntityDescription entityForName:@"ContactDB" inManagedObjectContext:[[AppManager sharedAppManager] managedObjectContext]];
[fetchRequest setEntity:callEntity];
NSMutableArray *predicateArray = [NSMutableArray array];
if(searchString.length)
{
// your search predicate(s) are added to this array
[predicateArray addObject:[NSPredicate predicateWithFormat:@"name CONTAINS[cd] %@", searchString]];
// finally add the filter predicate for this view
if(filterPredicate)
{
filterPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:filterPredicate, [NSCompoundPredicate orPredicateWithSubpredicates:predicateArray], nil]];
}
else
{
filterPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:predicateArray];
}
}
[fetchRequest setPredicate:filterPredicate];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:[[AppManager sharedAppManager] managedObjectContext]
sectionNameKeyPath:nil
cacheName:nil];
aFetchedResultsController.delegate = self;
NSError *error = nil;
if (![aFetchedResultsController performFetch:&error])
{
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return aFetchedResultsController;
}
我正在进行一些后台下载以更新UITableView中显示的数据。根据我在网络上发现的信息,我正在创建新的MOC以进行这些更改,我稍后将其与我的主要线程MOC合并。
- (void)managedObjectContextDidSave:(NSNotification *)notification {
dispatch_async(dispatch_get_main_queue(), ^{
if(notification.object != [[AppManager sharedAppManager] managedObjectContext]) {
[[[AppManager sharedAppManager] managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
}
});
}
对于合并代表我正在使用标准的Apple函数。
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;
[tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)
{
UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;
switch(type)
{
case NSFetchedResultsChangeInsert:
[tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)theIndexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;
switch(type)
{
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:theIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self fetchedResultsController:controller configureCell:[tableView cellForRowAtIndexPath:theIndexPath] atIndexPath:theIndexPath];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:theIndexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;
[tableView endUpdates];
}
问题1:通知到来,合并在UITableView中可见,但问题是过滤不适用于合并。正如你在我的过滤器中看到的那样,我正在跳过contactId = 1,但是当合并完成时,这个联系人会出现在UITableView中。
我仍然不确定是否有更好的方法来对另一个线程上的数据库所做的更改做出反应 - 比如重新加载表视图和重新获取UITableView数据。
问题2:当我在重新加载时快速滚动时,它会崩溃并显示消息:
Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x19762a40
只有当我在后台线程中删除并且它出现在负责创建UITableViewCell的代码中时才会发生 - 它在访问其中一个属性时崩溃。
修改
作为我做过的解决方法:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;
[tableView endUpdates];
[controller performFetch:nil];
[tableView reloadData];
}
所以我手动执行抓取并重新加载数据以再次过滤条目。
以下是我构建CoreData相关对象的方法:
- (NSManagedObjectModel *)managedObjectModel
{
if(_managedObjectModel != nil) {
return _managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Model" withExtension:@"momd"];
self.managedObjectModel = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] autorelease];
return _managedObjectModel;
}
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
self.managedObjectContext = [self createManagedObjectContextWithConcurrencyType:NSMainQueueConcurrencyType];
return _managedObjectContext;
}
- (NSManagedObjectContext *) createManagedObjectContextWithConcurrencyType:(NSManagedObjectContextConcurrencyType)concurrrencyType
{
NSManagedObjectContext* managedObjectContext = [[[NSManagedObjectContext alloc] initWithConcurrencyType:concurrrencyType] autorelease];
if(concurrrencyType == NSMainQueueConcurrencyType)
{
[managedObjectContext setPersistentStoreCoordinator:[self persistentStoreCoordinator]];
}
else
{
[managedObjectContext setParentContext:self.managedObjectContext];
}
return managedObjectContext;
}
以下是更新联系人的代码:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSManagedObjectContext* context = [[AppManager sharedAppManager] createManagedObjectContextWithConcurrencyType:NSPrivateQueueConcurrencyType];
//clear database
NSArray* allContacts = [context fetchObjectsForEntityName:@"ContactDB"];
if([allContacts count] > 0)
{
for (ContactDB* contact in allContacts)
{
[context deleteObject:contact];
}
}
NSArray* responseContacts = responseObject[@"contacts"];
[responseContacts enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSMutableDictionary* contactDict = [NSMutableDictionary dictionaryWithDictionary:obj];
ContactDB* contact = [[[ContactDB alloc] initWithDictionary:contactDict andContext:context] autorelease];
contact.order = [NSNumber numberWithInt:[order indexOfObject:contact.contactId]];
}];
[context save:nil];
[[[AppManager sharedAppManager] managedObjectContext] performBlock:^{
[[[AppManager sharedAppManager] managedObjectContext] save:nil];
}];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_LOADING_STOPPED object:self userInfo:nil];
});
});
答案 0 :(得分:1)
如果您要创建多个MOC,是否有理由不使用父/子设计?如果您使用的是父/子,则不需要合并更改。这是父母/孩子的好处之一。
从错误中,我怀疑你是在破坏线程边界规则。问题是我在哪里使用父母/子女的问题。
当您遇到此错误时,断点会在哪里触发?什么行代码负责触发该错误?这个代码行被触发了什么线程?
谓词结尾看起来是什么样的?您是否已将最终谓词打印到控制台以查看它并确保它按照您认为的方式构建?我在那里看到很多和/或组合。
切换到父母孩子后,你的崩溃消失了吗?我问,因为我看了你的代码进行合并,可能可疑。最好把它写成:
- (void)managedObjectContextDidSave:(NSNotification *)notification
{
NSManagedObjectContext *moc = [[AppManaged sharedAppManager] managedObjectContext];
[moc performBlock:^{
[moc mergeChangesFromContextDidSaveNotification:notification];
}];
}
通过这种方式,您可以保证在合并的正确队列中。
答案 1 :(得分:1)
经过两天的环顾四周,我从头开始准备了一个更简单的问题示例,最后我发现了一个问题。我仍然不明白为什么它从加载的数据库工作而不是更新,但我改变了:
NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"contactId != 1"];
要:
NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"contactId != %@", @"1"];
我理解第一个是不正确的,因为它不是NSString而我的contactId是NSString,但是为什么它在我加载应用程序时工作 - 当核心数据从磁盘加载时。对于合并和更新,NSPredicate与!= 1无法正常工作。
也许它会帮助某人 - 确保你尊重类型的谓词。
答案 2 :(得分:0)
我想请注意,您可以将另一个帖子上发布的NSNotification
对象传递给mergeChangesFromContextDidSaveNotification
,而无需发送。
此外,在更新联系人而不是调度的代码中,您应使用performBlock:
NSManagedObjectContext
方法,因为它是使用NSPrivateQueueConcurrencyType
并发类型创建的。
因此,想法是使用并发类型NSManagedObjectContext
创建NSPrivateQueueConcurrencyType
,并将其父级设置为主NSManagedObjectContext
。然后使用主MOC作为'NSFetchedResultsController',并使用私有MOC来更新数据。
[privateMOC performBlock:^{
// update your data
[privateMOC save:NULL]; // you should use NSError though
[privateMOC.parentContext performBlock:^{
[privateMOC.parentContext save:NULL]; // there your NSFetchedResultsController delegate methods should be fired automatically
}];
}];
如您所见,无需派遣。 而且,你真的应该使用ARC。我确信编译器比我们更有效率。
<强>更新强>
NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"contactId != 1"];
/*
Set up the fetched results controller.
*/
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] initWithEntityName:@"ContactDB"] autorelease];
if(searchString.length)
{
// your search predicate(s)
NSPredicate *searchTermPredicate = [NSPredicate predicateWithFormat:@"name CONTAINS[cd] %@", searchString];
// finally add the filter predicate for this view
filterPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:filterPredicate, searchTermPredicate, nil]];
}