Phi*_*iso 25 core-data objective-c uitableview nsfetchedresultscontroller ios
编辑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);
}
}
Run Code Online (Sandbox Code Playgroud)
编辑6:
iOS 8就在这里,这个问题又回来了.幸运的我.以前我把问题缩小到在表视图上使用estimatedRowHeight(顺便说一句,我从来没有完全解决问题.我刚停止使用estimatedRowHeight).现在我在不同情况下再次看到这个问题.几天前,当我将导航/标签栏设置为半透明时,我将其追踪到了一个提交.这包括在故事板中禁用"调整滚动视图插入"并选中复选框以使我的视图显示在顶部栏和底栏之下.我需要做一系列步骤来实现它,但我每次都可以通过这种方式配置故事板来重现它.如果我还原那个提交它就不再发生了.
虽然我说,"它不再发生",我真的认为这只是让它不太可能发生.这个bug是绝对的b****.我现在的直觉反应是这是一个iOS错误.我只是不知道我能做些什么来把它变成一个bug报告.这很疯狂.
编辑5:
如果你想阅读我的全部痛苦,请继续阅读这篇文章.如果你遇到这个问题而你只是想要一些帮助,这里有一些需要考虑的问题.
我的上一次编辑指出,当我使用基本的表视图单元格时,一切正常.我的下一步行动将是从头开始尝试逐个构建一个新的自定义单元格并查看它搞砸了.对于它的地狱,我重新启用我的旧自定义单元格代码,它工作得很好.唔?哦等等,我还是已经estimatedHeightForRowAtIndexPath
评论过了.当我删除这些评论并启用时estimatedHeightForRowAtIndexPath
,它再次变得糟透了.有趣.
我在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
Run Code Online (Sandbox Code Playgroud)
这是我看到的例外......
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)
Run Code Online (Sandbox Code Playgroud)
在我调用endUpdates时,我的应用程序实际上是在controllerDidChangeContent中遇到异常.我基本上看到了同样的事情(NSFetchedResultsController试图插入nil对象?),但我有更多的信息和案例,这是可重复的.我的所有合并事件都是插入.在合并期间,背景上下文似乎没有任何挂起的插入,删除或更新.我最初在整个地方都使用了performBlockAndWait,直到我从WWDC视频中了解了performBlock和performBlockAndWait之间的区别.我切换到performBlock,这让它变得更好一些.最初我认为这是一个线程问题,因为不完全理解块而导致它成为一个奇怪的内存问题的可能性,现在我又回到了竞争状态.看起来我只缺少一件作品.它有两种方式不会发生......
(1)注册上下文将保存通知,当我得到它时nil out 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];
Run Code Online (Sandbox Code Playgroud)
在大多数情况下,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];
}
Run Code Online (Sandbox Code Playgroud)
保存和合并的实现是它可能变得有趣的地方.期望从已经适当的performBlock中调用Save,因此它不会对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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
保存后,合并通知将被触发.我只是从后台合并到main,所以我只是想知道我是否可以内联合并调用或者我是否需要在performBlock内部进行.
- (void)backgroundManagedObjectContextDidSave:(NSNotification *)notification
{
if (![NSThread isMainThread]) {
[mainMOC performBlock:^ {
[self.mainMOC mergeChangesFromContextDidSaveNotification:notification];
}];
} else {
[mainMOC mergeChangesFromContextDidSaveNotification:notification];
}
}
Run Code Online (Sandbox Code Playgroud)
我获取的结果控制器委托方法很漂亮的锅炉板...
- (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];
}
Run Code Online (Sandbox Code Playgroud)
另一段可能感兴趣的代码.我正在为表视图单元格使用autolayout,为动态单元格高度使用新的estimatedHeightForRowAtIndexPath 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;
}
Run Code Online (Sandbox Code Playgroud)
现在已经坚持了将近一个星期.在这个过程中学到了很多,但是我已经准备好继续前进了.任何建议将不胜感激.
编辑
我整理了一个非常大的调试日志,试图讲述udpates发生了什么.我看到一些非常奇怪的东西.我一次用50行更新表,所以我只会包含调试输出的有趣部分.每次配置一个单元格时,我都会打印出刚出列的单元格的标题以及新标题的内容.当我点击表格视图中的最后一个单元格时,我向Web服务查询以获取更多数据.在我遇到异常之前,此输出与最终更新有关...
// Lots of output was here that I omitted
configure cell at sect 5 row 18 WAS Suphala NOW Keller Williams
configure cell at sect 5 row 19 WAS Advocate Of Wordz NOW Gates
configure cell at sect 5 row 20 WAS Emanuel and the Fear NOW Beats Antique
configure cell at sect 5 row 21 WAS The Julie Ruin NOW Ashrae Fax
// At this point I hit the end of the table and query for more data - for some reason row 18 gets configured again. Possibly no big deal.
configure cell at sect 5 row 18 WAS Keller Williams NOW Keller Williams
configure cell at sect 5 row 22 WAS Old Wounds NOW Kurt Vile
JSON size 100479
Starting JSON parsing
page 3 of 15. total events 709. events per page 50. current low idx 100 next trigger idx 149
// Parsing data finished, saving background context
Saving managed object context <NSManagedObjectContext: 0x17e912f0>
Background context will save
Finished saving managed object context <NSManagedObjectContext: 0x17e912f0>
Merging background context into main context
JSON parsing finished
** controllerWillChangeContent called **
** BEGIN UPDATES triggered **
inserting SECTION 6
inserting SECTION 7
inserting SECTION 8
inserting ROW sect 5 row 17
inserting ROW sect 5 row 22
inserting ROW sect 5 row 25
inserting ROW sect 5 row 26
inserting ROW sect 5 row 27
inserting ROW sect 5 row 28
inserting ROW sect 5 row 29
// A bunch more rows added here that I omitted
** controllerDidChangeContent called **
// This configure cell happens before the endUpdates call has completed
configure cell at sect 5 row 18 WAS Conflict NOW Conflict
Run Code Online (Sandbox Code Playgroud)
在最后的更新中,它试图在s5 r17插入,但我已经在该行有一个单元格.它也试图在s5 r22插入,但我也已在该行有一个单元格.最后,它在s5 r25插入一行,实际上是一个新行.在我看来,考虑到r17和r22作为插入在表格中留下了一个空白.那些索引的先前单元格是否应该将事件移动到r23和r24?
我的获取结果控制器正在使用按日期和开始时间排序的排序描述符.也许r17和r22的现有事件没有获得移动事件,因为没有任何与NSManagedObjects相关的更改.从本质上讲,他们需要移动,因为我的排序描述符比他们更早的事件,而不是因为他们的数据发生了变化.
编辑2:
看起来那些插入只会触发现有单元格向下移动:(
编辑3:
我今天尝试的事情......
编辑4:
现在它变得有趣了.
我尝试删除表视图中的随机组件,例如刷新控件.还试图摆脱我对estimatedHeightForRowAtIndexPath的使用,这意味着只提供静态行高而不是使用autolayout来确定动态行高.这两个都没有发现.我也尝试完全摆脱我的自定义单元格,只使用基本的表格视图单元格.
那很有效.
我尝试了一个带字幕的基本表格视图单元格.
那很有效.
我尝试了一个带有字幕和图像的基本表格视图单元格.
那很有效.
我的堆栈跟踪顶部靠近所有这些与动画相关的项目开始变得更有意义.看起来这是与自动布局相关的.
来自 Apple 技术支持工程师:
\n\n\n\n\n为了保护数据存储的完整性,Core Data 捕获其操作期间发生的一些\n 异常。有时这意味着,如果 Core Data 通过委托方法调用您的代码,Core Data 可能最终会捕获您的代码抛出的异常。
\n\n多线程错误是神秘核心数据问题的最常见原因。
\n
在这种情况下,Core Data 通过您的方法捕获了controllerDidChangeContent:
由于尝试使用insertObject:atIndex
.
最可能的解决方法是确保所有NSManagedObject
代码都封装在内部performBlock:
或performBlockAndWait:
调用中。
在 iOS 8 和 OSX Yosemite 中,Core Data 能够检测和报告对其并发模型的违规行为。每当您的应用程序访问托管对象上下文或来自错误调度队列的托管对象时,它都会抛出异常。-com.apple.CoreData.ConcurrencyDebug 1
您可以通过 Xcode\xca\xbcs 方案编辑器在命令行上传递到您的应用程序来启用断言。
Ole Begemann对新功能有一篇精彩的文章。
\n 归档时间: |
|
查看次数: |
2660 次 |
最近记录: |