核心数据 NSObjectInaccessibleException NSManagedObject 已失效
Posted
技术标签:
【中文标题】核心数据 NSObjectInaccessibleException NSManagedObject 已失效【英文标题】:Core-Data NSObjectInaccessibleException NSManagedObject has been invalidated 【发布时间】:2011-08-28 14:27:59 【问题描述】:我有一些代码可以将课程下载为 JSON、解析它们并将它们放入核心数据中。然后将它们显示在 UITableView 中。目前,当用户有很多课程时,连接有时会超时。因此,我试图在课程进入时解析它们(使用 SBJson),并一次将它们添加到 tableview 中。
两者的代码基本相同,但是新的代码导致tableView的东西启动时会崩溃,并报错
"Terminating app due to uncaught exception
'NSObjectInaccessibleException', reason: 'The NSManagedObject with ID:0x5ad0570 <x-coredata://A21AC71F-175B-423D-BF7D-C67BEE094460/Lessons/p18> has been invalidated.'"
我想知道这两个可能导致此错误的代码清单之间的区别是什么。原始代码在循环中创建每个核心数据对象,但新代码在下载每个核心数据对象时创建它。 listViewArray 是用于填充 UITableView 的数组。
我正在使用 SBJsonStreamParser 和 SBJsonStreamParserAdapter 来解析 Json。
我确实有一个有效的实现(未显示),它基本上每次接收到新对象时都会调用下面的原始代码(每次都运行接收到的对象的完整循环)。不过,我想知道是什么导致了错误,而不仅仅是让某些东西正常工作。
这是原始的非流式代码,在 connectionDidFinishLoading 中调用:
NSMutableArray *tempListArray = [[NSMutableArray alloc] initWithArray:jsonStreamedData];
if (listViewArray)
[listViewArray release];
listViewArray = [[NSMutableArray alloc] init];
if(![tempListArray count])
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Message" message:@"No active lessons " delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
[alertView show];
[alertView release];
else
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
NSError *error = nil;
[appDelegate.managedObjectContext reset];
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
for (int i = 0; i < [tempListArray count]; i++)
NSFetchRequest *checkRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *lessonEntity = [NSEntityDescription entityForName:@"Lessons" inManagedObjectContext:managedObjectContext];
[checkRequest setEntity:lessonEntity];
NSPredicate *langPredicate = [NSPredicate predicateWithFormat:@"(language = %@)", appDelegate.currentLanguage];
NSPredicate *userPredicate = [NSPredicate predicateWithFormat:@"(username = %@)", appDelegate.userName];
NSPredicate *idPredicate = [NSPredicate predicateWithFormat:@"(content_Id = %@)", [[tempListArray objectAtIndex:i] valueForKey:@"id"]];
[checkRequest setPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:langPredicate, userPredicate, idPredicate, nil]]];
NSArray *checkResults = [managedObjectContext executeFetchRequest:checkRequest error:&error];
[checkRequest release];
NSMutableDictionary *tempDict = [[NSMutableDictionary alloc] init];
if ([checkResults count])
Lessons *lessonObj = [checkResults objectAtIndex:0];
lessonObj.cards_count = [[tempListArray objectAtIndex:i] valueForKey:@"cards_count"];
lessonObj.mTitle = [[tempListArray objectAtIndex:i] valueForKey:@"title"];
lessonObj.sound_Url = [[tempListArray objectAtIndex:i] valueForKey:@"audio_url"];
lessonObj.mId = [NSNumber numberWithInt:i];
[tempDict setValue:lessonObj forKey:@"lesson"];
[tempDict setValue: [[tempListArray objectAtIndex:i] objectForKey:@"image_url"] forKey:@"image_url"];
[listViewArray addObject:tempDict];
else
Lessons *newLesson = (Lessons *)[NSEntityDescription insertNewObjectForEntityForName:@"Lessons" inManagedObjectContext:appDelegate.managedObjectContext];
newLesson.cards_count = [[tempListArray objectAtIndex:i] valueForKey:@"cards_count"];
newLesson.mTitle = [[tempListArray objectAtIndex:i] valueForKey:@"title"];
newLesson.sound_Url = [[tempListArray objectAtIndex:i] valueForKey:@"audio_url"];
newLesson.content_Id = [[tempListArray objectAtIndex:i] valueForKey:@"id"];
newLesson.username = appDelegate.userName;
newLesson.language = appDelegate.currentLanguage;
newLesson.mId = [NSNumber numberWithInt:i];
[tempDict setValue:newLesson forKey:@"lesson"];
[tempDict setValue: [[tempListArray objectAtIndex:i] objectForKey:@"image_url"] forKey:@"image_url"];
[listViewArray addObject:tempDict];
[tempDict release];
tempDict = nil;
if (![appDelegate.managedObjectContext save:&error])
NSLog(@"Core Data Error - %@", [error localizedDescription]);
// NSMutableArray *tempArray = [NSMutableArray arrayWithArray:listViewArray];
// [listViewArray removeAllObjects];
// [listViewArray addObjectsFromArray:[[tempArray reverseObjectEnumerator] allObjects]];
// tempArray = nil;
[tempListArray release];
[mListsTableView reloadData];
这是崩溃的代码,在 parser:foundObject 中调用:循环代码已被删除,因为每次下载新的 Json 对象时都会调用它。
[jsonStreamedData addObject:dict];
if (!listViewArray)
listViewArray = [[NSMutableArray alloc] init];
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
NSError *error = nil;
[appDelegate.managedObjectContext reset];
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
NSFetchRequest *checkRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *lessonEntity = [NSEntityDescription entityForName:@"Lessons" inManagedObjectContext:managedObjectContext];
[checkRequest setEntity:lessonEntity];
NSPredicate *langPredicate = [NSPredicate predicateWithFormat:@"(language = %@)", appDelegate.currentLanguage];
NSPredicate *userPredicate = [NSPredicate predicateWithFormat:@"(username = %@)", appDelegate.userName];
NSPredicate *idPredicate = [NSPredicate predicateWithFormat:@"(content_Id = %@)", [dict valueForKey:@"id"]];
[checkRequest setPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:langPredicate, userPredicate, idPredicate, nil]]];
NSArray *checkResults = [managedObjectContext executeFetchRequest:checkRequest error:&error];
[checkRequest release];
NSMutableDictionary *tempDict = [[NSMutableDictionary alloc] init];
if ([checkResults count])
Lessons *lessonObj = [checkResults objectAtIndex:0];
lessonObj.cards_count = [dict valueForKey:@"cards_count"];
lessonObj.mTitle = [dict valueForKey:@"title"];
lessonObj.sound_Url = [dict valueForKey:@"audio_url"];
lessonObj.mId = [NSNumber numberWithInt:jsonStreamedData.count - 1]; // This should be equivalent to i from the loop in the first code
[tempDict setValue:lessonObj forKey:@"lesson"];
[tempDict setValue: [dict objectForKey:@"image_url"] forKey:@"image_url"];
[listViewArray addObject:tempDict];
else
Lessons *newLesson = (Lessons *)[NSEntityDescription insertNewObjectForEntityForName:@"Lessons" inManagedObjectContext:appDelegate.managedObjectContext];
newLesson.cards_count = [dict valueForKey:@"cards_count"];
newLesson.mTitle = [dict valueForKey:@"title"];
newLesson.sound_Url = [dict valueForKey:@"audio_url"];
newLesson.content_Id = [dict valueForKey:@"id"];
newLesson.username = appDelegate.userName;
newLesson.language = appDelegate.currentLanguage;
newLesson.mId = [NSNumber numberWithInt:jsonStreamedData.count - 1];
[tempDict setValue:newLesson forKey:@"lesson"];
[tempDict setValue: [dict objectForKey:@"image_url"] forKey:@"image_url"];
[listViewArray addObject:tempDict];
[tempDict release];
tempDict = nil;
if (![appDelegate.managedObjectContext save:&error])
ALog(@"Core Data Error - %@", [error localizedDescription]);
// NSMutableArray *tempArray = [NSMutableArray arrayWithArray:listViewArray];
// [listViewArray removeAllObjects];
// [listViewArray addObjectsFromArray:[[tempArray reverseObjectEnumerator] allObjects]];
// tempArray = nil;
//[self getListsLocally];
[mListsTableView reloadData];
最后,这里是使用第二个列表时崩溃的实际部分,在 tableView:cellForRowAtIndexPath: 顺便说一句,它在 row == 1 时崩溃,而不是 row == 0。由于某种原因,第 0 行还可以。 . 当然,它永远没有机会加载其他行。
titleLabel.text = [[[listViewArray objectAtIndex:indexPath.row] valueForKey:@"lesson"] valueForKey:@"mTitle"]; // CRASH!
labelCards.text = [NSString stringWithFormat:@"%@ Cards", [[[listViewArray objectAtIndex:indexPath.row] valueForKey:@"lesson"] valueForKey:@"cards_count"]];
if([[listViewArray objectAtIndex:indexPath.row] objectForKey:@"userImageObj"] == nil)
mImageView.backgroundColor = [UIColor grayColor];
if ([[listViewArray objectAtIndex:indexPath.row] objectForKey:@"isThreadLaunched"] == nil)
[NSThread detachNewThreadSelector:@selector(loadImagesInBackground:) toTarget:self withObject:[NSNumber numberWithInt:indexPath.row]];
[[listViewArray objectAtIndex:indexPath.row] setObject:@"Yes" forKey:@"isThreadLaunched"];
else
mImageView.image = [[listViewArray objectAtIndex:indexPath.row] objectForKey:@"userImageObj"];
【问题讨论】:
【参考方案1】:当您在运行提取之前立即在 managedObjectContext 上调用 reset
时,最有可能发生对象失效。调用reset
会使内存中的对象无效,但在保存之前不会删除它们。如果一个无效的托管对象被另一个对象(例如数组)保留,那么它将在保存后以无效的形式持续存在。当您运行提取时,提取返回无效对象,当您尝试访问它们的属性之一时会导致错误
reset
在与撤消管理器一起使用时被调用。它不是通用的“擦除上下文”调用。如果要删除现有对象,则需要获取它们并显式删除它们。
您的代码还有一些其他问题。即使您没有创建它,您也可以在 checkRequest
数组上调用 release
。这可能会导致数组随机消失。同样,listViewArray
似乎是一个类属性,但您从不使用访问器形式,例如self.listViewArray
以确保正确保留。
【讨论】:
【参考方案2】:最可能的原因是解析器没有在主线程上运行,并且您在主线程和解析器中使用相同的 ManagedObjectContext - 您不能这样做 - 保证会导致各种奇怪的行为。
您需要在解析器中创建一个新的ManagedObjectContext,并将其与主线程的ManagedObjectContext的persistentStore相关联
CoreData 框架对此非常清楚 - 你不能跨线程边界共享 ManagedObjectContext
【讨论】:
以上是关于核心数据 NSObjectInaccessibleException NSManagedObject 已失效的主要内容,如果未能解决你的问题,请参考以下文章