
[英]NSFetchedResultsController not tracking CoreData changes from the app extension
[英]CoreData and NSFetchedResultsController with merging changes
我正在使用第一个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];
}
因此,我正在手动执行Fetch和reloadingData来再次过滤条目。
这是我构造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];
});
});
如果要创建多个MOC,是否有理由不使用父/子设计? 如果您使用的父/子那么你并不需要合并的变化。 这是父母/子女的好处之一。
从错误中,我怀疑您正在违反线程边界规则。 问题是我的有关使用父母/孩子的问题。
当您遇到此错误时,断点将向何处发射? 哪一行代码负责触发该错误? 该行代码被触发在哪个线程上?
结束时谓词看起来像什么? 您是否已将最终谓词打印到控制台上以进行查看并确保它正在按照您的预期方式构建? 我在那里看到很多和/或组合。
您的当机事故是否因转为父母子女而消失了? 我问是因为我查看了您的合并代码,因此可能会令人怀疑。 最好将其编写为:
- (void)managedObjectContextDidSave:(NSNotification *)notification
{
NSManagedObjectContext *moc = [[AppManaged sharedAppManager] managedObjectContext];
[moc performBlock:^{
[moc mergeChangesFromContextDidSaveNotification:notification];
}];
}
这样,可以确保您在正确的队列中进行合并。
经过两天的观察,我从头准备了一个简单的问题示例,终于找到了问题。 我仍然不明白为什么它不能从加载的数据库中运行而不进行更新,但是我改变了:
NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"contactId != 1"];
至:
NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"contactId != %@", @"1"];
我知道第一个是不正确的,因为它不是NSString,而我的contactId是NSString,但是当我加载应用程序时(当从磁盘加载核心数据时)为什么它可以工作。 对于合并和更新,NSPredicate的!= 1无效。
也许它将对某人有所帮助-确保您尊重谓词类型。
我想指出,您可以将发布在另一个线程上的NSNotification
对象传递给mergeChangesFromContextDidSaveNotification
而无需进行调度。
另外,在更新联系人而不是调度联系人的代码中,应该使用NSManagedObjectContext
performBlock:
方法,因为它是使用NSPrivateQueueConcurrencyType
并发类型创建的。
因此,想法是创建并发类型为NSPrivateQueueConcurrencyType
NSManagedObjectContext
,并将其父级设置为您的主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]];
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.