Ios NSFetchedResultsController试图插入nil对象

Ios NSFetchedResultsController试图插入nil对象,ios,objective-c,uitableview,core-data,nsfetchedresultscontroller,Ios,Objective C,Uitableview,Core Data,Nsfetchedresultscontroller,编辑7: 这是我的保存方法。这是一个很简单的例子。DEBUG_LOG()宏仅在调试生成时执行 - (void)saveManagedObjectContext:(NSManagedObjectContext *)moc { if ([moc hasChanges]) { DEBUG_LOG(@"Saving managed object context %@", moc); NSError *error; BOOL success = [m

编辑7:

这是我的保存方法。这是一个很简单的例子。DEBUG_LOG()宏仅在调试生成时执行

- (void)saveManagedObjectContext:(NSManagedObjectContext *)moc
{
    if ([moc hasChanges]) {
        DEBUG_LOG(@"Saving managed object context %@", moc);
        NSError *error;
        BOOL success = [moc save:&error];
        if (!success || error) {
            DEBUG_LOG(@"ERROR: Couldn't save to managed object context %@: %@",
                  moc, error.localizedDescription);
        }
        DEBUG_LOG(@"Finished saving managed object context %@", moc);
    } else {
        DEBUG_LOG(@"Managed object context %@ had no changes", moc);
    }
}
编辑6:

iOS 8出现了,这个问题又出现了。我真幸运。之前,我已经将问题缩小到在表视图上使用estimatedRowHeight(顺便说一句,我从未完全解决过这个问题。我只是停止使用estimatedRowHeight)。现在我在不同的情况下又看到了这个问题。我追踪到几天前的一次提交,当时我将导航/标签栏设置为半透明。这包括禁用故事板中的“调整滚动视图插入”,并选中复选框,使我的视图显示在顶部栏和底部栏下。我需要做一系列的步骤来实现这一点,但我每次都可以通过这样配置我的故事板来重现它。如果我恢复该提交,它将不再发生

当我说“它不再发生”的时候,我真正想的是这只是让它不太可能发生。这个bug是绝对的b****。我现在的直觉反应是这是一个iOS错误。我只是不知道我能做些什么把它变成一个bug报告。这太疯狂了

编辑5:

如果你想阅读我的全部痛苦,请继续阅读这篇文章。如果你遇到了这个问题,你只是想得到一些帮助,这里有一些事情要调查

我上次编辑时注意到,当我使用基本的表视图单元格时,一切都很好。我下一步要做的是从头开始,一块一块地建造一个新的定制电池,看看它在哪里出了问题。见鬼,我重新启用了旧的自定义单元格代码,它工作得很好。嗯?哦,等等,我仍然有
estimatedheightforrowatinexpath
注释掉了。当我删除这些注释并启用
EstimatedHeightForrowatineXpath
时,它又变得糟糕了。有趣

我在API文档中查找了该方法,它提到了一个名为
UITableViewAutomaticDimension
的常量。我估计的值实际上只是普通细胞高度的一个,所以切换到那个常数不会有什么坏处。切换到该常数后,它工作正常。没有奇怪的异常/图形故障需要报告

原创帖子

我有一个非常标准的iPhone应用程序,它在后台从web服务获取数据,并在表视图中显示数据。后台更新工作为NSPrivateQueueConcurrencyType配置了自己的托管对象上下文。“我的表视图”的“获取结果”控制器为NSMainQueueConcurrencyType配置了自己的托管对象上下文。当后台上下文解析新数据时,它通过
mergeChangesFromContextDidSaveNotification
将该数据传递到主上下文。有时在合并过程中,我的应用程序在此处遇到异常

Thread 1, Queue : com.apple.main-thread
#0  0x3ac1b6a0 in objc_exception_throw ()
#1  0x308575ac in -[__NSArrayM insertObject:atIndex:] ()
#2  0x33354306 in __46-[UITableView _updateWithItems:updateSupport:]_block_invoke687 ()
#3  0x330d88d2 in +[UIView(UIViewAnimationWithBlocks)     _setupAnimationWithDuration:delay:view:options:factory:animations:start:animationStateGenerator:completion:] ()
#4  0x330ef7e4 in +[UIView(UIViewAnimationWithBlocks) animateWithDuration:delay:options:animations:completion:] ()
#5  0x3329e908 in -[UITableView _updateWithItems:updateSupport:] ()
#6  0x332766c6 in -[UITableView _endCellAnimationsWithContext:] ()
#7  0x0005ae72 in -[ICLocalShowsTableViewController controllerDidChangeContent:] at ICLocalShowsTableViewController.m:475
#8  0x3069976c in -[NSFetchedResultsController(PrivateMethods) _managedObjectContextDidChange:] ()
#9  0x308dfe78 in __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ ()
#10 0x30853b80 in _CFXNotificationPost ()
#11 0x3123a054 in -[NSNotificationCenter postNotificationName:object:userInfo:] ()
#12 0x306987a2 in -[NSManagedObjectContext(_NSInternalNotificationHandling) _postObjectsDidChangeNotificationWithUserInfo:] ()
#13 0x306f952a in -[NSManagedObjectContext _mergeChangesFromDidSaveDictionary:usingObjectIDs:] ()
#14 0x306f9734 in -[NSManagedObjectContext mergeChangesFromContextDidSaveNotification:] ()
#15 0x0006b5be in __65-[ICManagedObjectContexts backgroundManagedObjectContextDidSave:]_block_invoke at ICManagedObjectContexts.m:133
#16 0x306f9854 in developerSubmittedBlockToNSManagedObjectContextPerform ()
#17 0x3b1000ee in _dispatch_client_callout ()
#18 0x3b1029a8 in _dispatch_main_queue_callback_4CF ()
#19 0x308e85b8 in __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ ()
#20 0x308e6e84 in __CFRunLoopRun ()
#21 0x30851540 in CFRunLoopRunSpecific ()
#22 0x30851322 in CFRunLoopRunInMode ()
#23 0x355812ea in GSEventRunModal ()
#24 0x331081e4 in UIApplicationMain ()
#25 0x000554f4 in main at main.m:16
这是我看到的一个例外

CoreData: error: Serious application error.  An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:.  *** -[__NSArrayM insertObject:atIndex:]: object cannot be nil with userInfo (null)
在我调用endUpdates时,我的应用程序实际上遇到了controllerDidChangeContent中的异常。我基本上看到了与此()相同的东西,但我有更多的信息和案例a,它们是可复制的。我的所有合并事件都是插入。在合并过程中,后台上下文中似乎没有任何挂起的插入、删除或更新。我最初在各地使用performBlock和wait,直到我从WWDC视频中了解到performBlock和performBlock和wait之间的区别。我切换到了performBlock,这让它变得更好了一点。起初,我把这当作一个线程问题来处理,后来我认为这可能是一个奇怪的内存问题,是由于没有完全理解块造成的,现在我又回到了竞争状态。看来我只缺了一块。有两种方式不发生这种情况

(1) 注册上下文将保存通知,获取FRC委托时将其置零,并在合并后将该委托设置回原位。这离完全不使用FRC不远了,所以这确实不是一个解决方案

(2) 执行足够长时间阻塞主线程的操作,这样就不会发生争用情况。例如,当我将大量调试日志消息添加到我的表视图委托中时,这会使其速度减慢到足以使其不发生的程度

以下是我认为重要的代码片段(我缩短了某些点以缩小这个已经很大的帖子)

在滚动过程中的各个点之后,视图控制器将通过调用包含该点的函数来请求更多数据

AFJSONRequestOperation *operation =
    [AFJSONRequestOperation JSONRequestOperationWithRequest:request
        success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
            // Parsing happens on MOC background queue
            [backgroundMOC performBlock:^ {
                [self parseJSON:JSON];

                // Handle everything else on the main thread
                [mainMOC performBlock:^ {
                    if (completion) {
                        // Remove activitiy indicators and such from the main thread
                    }
                }];
            }];
        }

        failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
            [[NSOperationQueue mainQueue] performBlock:^ {
                if (completion) {
                    // Remove activitiy indicators and such from the main thread
                }
                // Show an alert view saying that the request failed
            }];
        }
     ];

[operation setCacheResponseBlock:^NSCachedURLResponse *(NSURLConnection *connection, NSCachedURLResponse *cachedResponse) {
    return nil;
}];

[_operationQueue addOperation:operation];
在大多数情况下,parseJSON实际上没有什么有趣的东西

- (void)parseJSON:(NSDictionary *)json
{       
    NSError *error;
    NSArray *idExistsResults;
    NSNumber *eventId;
    NSFetchRequest *idExistsFetchRequest;
    LastFMEvent *event;
    NSManagedObjectModel *model = backgroundMOC.persistentStoreCoordinator.managedObjectModel;
    for (NSDictionary *jsonEvent in jsonEvents) {
        eventId = [NSNumber numberWithInt:[jsonEvent[@"id"] intValue]];
        idExistsFetchRequest = [model fetchRequestFromTemplateWithName:kGetEventByIDFetchRequest substitutionVariables:@{@"eventID" : eventId}];
        idExistsResults  = [backgroundMOC executeFetchRequest:idExistsFetchRequest error:&error];
        // Here I check for errors - omitted that part

        if ([idExistsResults count] == 0) {
            // Add a new event
            event = [NSEntityDescription insertNewObjectForEntityForName:[LastFMEvent entityName] inManagedObjectContext:backgroundMOC];
            [event populateWithJSON:jsonEvent];
        } else if ([idExistsResults count] == 1) {
            // Get here if I knew about the event already, so I update a few fields
        }
    }
    [self.mocManager saveManagedObjectContext:backgroundMOC];
}
save和merge的实现可能会让人感兴趣。Save希望已经从相应的performBlock中调用,因此它不会对performBlock执行任何操作

- (void)saveManagedObjectContext:(NSManagedObjectContext *)moc
{
    if ([moc hasChanges]) {
        NSError *error;
        BOOL success = [moc save:&error];
        if (!success || error) {
            NSLog(@"ERROR: Couldn't save to managed object context %@: %@",
                  moc, error.localizedDescription);
        }
    }
}
- (void)backgroundManagedObjectContextDidSave:(NSNotification *)notification
{
    if (![NSThread isMainThread]) {
        [mainMOC performBlock:^ {
            [self.mainMOC mergeChangesFromContextDidSaveNotification:notification];
        }];
    } else {
        [mainMOC mergeChangesFromContextDidSaveNotification:notification];
    }
}
保存后,将触发合并通知。我只是从后台合并到主界面,所以我非常想知道我是否可以内联合并调用,或者是否需要在performBlock中进行合并

- (void)saveManagedObjectContext:(NSManagedObjectContext *)moc
{
    if ([moc hasChanges]) {
        NSError *error;
        BOOL success = [moc save:&error];
        if (!success || error) {
            NSLog(@"ERROR: Couldn't save to managed object context %@: %@",
                  moc, error.localizedDescription);
        }
    }
}
- (void)backgroundManagedObjectContextDidSave:(NSNotification *)notification
{
    if (![NSThread isMainThread]) {
        [mainMOC performBlock:^ {
            [self.mainMOC mergeChangesFromContextDidSaveNotification:notification];
        }];
    } else {
        [mainMOC mergeChangesFromContextDidSaveNotification:notification];
    }
}
我获取的结果控制器委托方法非常简单

- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{
    UITableView *tableView = self.tableView;

    switch (type) {

        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:@[newIndexPath]
                             withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:@[indexPath]
                             withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
        case NSFetchedResultsChangeUpdate:
            [self configureCell:(ICLocalShowsTableViewCell *)[tableView cellForRowAtIndexPath:indexPath]
                          atIndexPath:indexPath];
            break;
        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:@[indexPath]
                             withRowAnimation:UITableViewRowAnimationAutomatic];
            [tableView insertRowsAtIndexPaths:@[newIndexPath]
                             withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
    }
}

- (void)controller:(NSFetchedResultsController *)controller
  didChangeSection:(id )sectionInfo
           atIndex:(NSUInteger)sectionIndex
     forChangeType:(NSFetchedResultsChangeType)type
{
    switch(type) {

        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                          withRowAnimation:UITableViewRowAnimationAutomatic];
            break;

        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                          withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
    }
}

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    [self.tableView beginUpdates];
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    [self.tableView endUpdates];
}
另一段可能会引起兴趣的代码。我正在为我的表视图单元格使用autolayout,为动态单元格高度使用新的EstimatedHeightForrowatineXpath API。这意味着在调用[self.tableView endUpdates]的过程中,最后一步实际上涉及到一些托管对象,而对节数/行数的其他调用只需要知道FRC的计数

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSAssert([NSThread isMainThread], @"");
    LastFMEvent *event = [self.fetchedResultsController objectAtIndexPath:indexPath];

    if (!_offscreenLayoutCell) {
        _offscreenLayoutCell = [self.tableView dequeueReusableCellWithIdentifier:kLocalShowsCellIdentifier];
    }

    [_offscreenLayoutCell configureWithLastFMEvent:event];
    [_offscreenLayoutCell setNeedsLayout];
    [_offscreenLayoutCell layoutIfNeeded];

    CGSize cellSize = [_offscreenLayoutCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
    return cellSize.height;
}
我已经在这上面呆了将近一个星期了。在这个过程中我学到了很多,但天啊,我准备继续前进。如有任何建议,将不胜感激

编辑

我整理了一个相当大的调试日志,试图讲述udpates的故事。我看到了一些非常奇怪的事情。我一次更新50行的表,所以我只包含利息