Ios 合并更改的CoreData和NSFetchedResultsController

Ios 合并更改的CoreData和NSFetchedResultsController,ios,objective-c,uitableview,core-data,Ios,Objective C,Uitableview,Core Data,我正在与我的第一个CoreData应用程序抗争,我遇到了几个问题——我觉得我做错了什么 在主线程上,我有一个带有NSFetchedResultsController的UITableView,使用以下代码创建: - (NSFetchedResultsController *)newFetchedResultsControllerWithSearch:(NSString *)searchString { NSSortDescriptor *sort = [[[NSSortDescriptor all

我正在与我的第一个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中显示的数据。根据我在web上找到的信息,我正在创建新的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];
}
只有当我在后台线程中删除并且它出现在负责创建UITableViewCell的代码中时,才会发生这种情况—它在访问其中一个属性时崩溃

编辑

作为一种解决方法,我已经制定了:

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;
[tableView endUpdates];

[controller performFetch:nil];
[tableView reloadData];
}
因此,我手动执行fetch并重新加载数据以再次筛选条目

以下是我如何构造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,是否有不使用父/子设计的原因?如果您使用的是父级/子级,则不需要合并更改。这是父母/子女的好处之一

从错误中,我怀疑您违反了线程边界规则。问题是我关于使用父/子的问题在哪里

当你遇到这个错误时,断点在哪里?触发该错误的是哪一行代码?那行代码是在哪个线程上触发的

更新 谓词在这个结尾看起来像什么?您是否已将最终谓词打印到console以查看它,并确保它按照您认为的方式构建?我在那里看到很多和/或组合

你的崩溃是不是因为换成了亲子关系?我问是因为我看了你的合并代码,它可能是可疑的。最好写为:

- (void)managedObjectContextDidSave:(NSNotification *)notification 
{
    NSManagedObjectContext *moc = [[AppManaged sharedAppManager] managedObjectContext];
    [moc performBlock:^{
        [moc mergeChangesFromContextDidSaveNotification:notification];
    }];
}

这样,您就可以保证处于正确的合并队列中。

我想指出的是,您可以将另一个线程上发布的
NSNotification
对象从ContextDidSaveNotification传递到
MergeChanges,而无需发送

此外,在更新联系人而不是分派联系人的代码中,您应该使用
NSManagedObjectContext
performBlock:
方法,因为它是使用
nsprivatequeueconcurrency类型创建的

因此,我们的想法是使用并发类型创建
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
    }];
}];
如您所见,没有必要进行调度。 而且,你真的应该使用弧。我相信编译器比我们更高效

更新

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]];
}

经过两天的环顾,我从头开始准备了一个简单的问题示例,最后我发现了一个问题。我仍然不明白为什么它是从加载的数据库工作的,不是为了更新,但我改变了:

NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"contactId != 1"];
致:

我知道第一个是不正确的,因为它不是NSString,我的contactId是NSString,但为什么它在我加载应用程序时工作——当核心数据从磁盘加载时。对于合并和更新,NSPredicate with!=我不工作


也许它会帮助某些人-确保您尊重类型的谓词。

嘿,谢谢您的建议。我已经尝试过使用parent/child,但当我将child与parent合并时,它会阻塞主线程很长一段时间,并且通常需要更长的时间来更新(我正在使用大约5000个条目进行更新)。我在父级/子级上遇到一些问题-我将更改为该方法并发布代码。当我尝试访问CoreData ManagedObject属性之一时,此错误出现在创建单元格的代码上。错误发生在哪个线程上?如果您在UI代码中不在线程零上,那么线程是您的问题。如果您在P/C和线程方面存在性能问题,那么您应该专注于解决该问题,而不是避免它,因为您只是将性能影响传递到其他地方。将更新分成小块通常是最简单的答案。FetchResultController在合并时忽略NSPredicates的问题如何?家长/孩子的问题也会解决?对不起,我没有得到第二条提示。关于在主队列上调度-我发现建议合并必须在主线程上完成-以正确获得UI更新。我建议也使用父/子上下文。现在我将用它的例子更新我的答案。我没有使用ARC-几个月前我在另一个项目中遇到了很多关于ARC性能的问题-所以我更喜欢手动处理;)@GrzegorzKrukowski更新了我的答案。确保您设置了父MOC。谢谢您的代码。就问题而言,这与我的工作原理完全相同:)仍然使用contactId进行预测!=1被完全忽略,并且在激发NSFetchedResultsController时,不符合NSPredicates的对象将添加到UITable:(你真的需要检查这些保存。传入一个错误并检查它。如果你不这样做,你就有可能隐藏一个错误,你将无法检测或正确地作出反应。Objective-C任何时候要求一个错误指针,设置它并检查它。传入
nil
否定任何其他调试。是的,我知道这只是为了加快测试速度ing—我对编码进行了10次重构,以了解发生了什么—但我知道这并没有什么错误:)即使是在演示幻灯片中,我也会将错误代码放进去。使用宏完成应用程序使其更容易。我只需将错误代码放回去,就解决了代码审查中的更多问题。即使是在演示幻灯片中,我也会将错误代码放进去。同样的问题导致我的
NSFetchedResultsController
在运行时只注意删除而不注意插入合并在
NSPersistentContainer
中所做的更改
NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"contactId != 1"];
NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"contactId != %@", @"1"];