Pergunta

I have some code that downloads lessons as JSON, parses them, and puts them into core data. They are then displayed in a UITableView. Currently, when a user has many lessons the connection sometimes times out. So I am attempting to parse the lessons as they come in (using SBJson) and add them to the tableview one at a time.

The code for the two is basically the same, but the new code results in crashing when the tableView stuff kicks in, with the error

"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.'"

I would like to know what the difference is between these two code listings that might be causing this error. The original code creates each core data object in a loop, but the new code creates each core data object as it is downloaded. listViewArray is the array that is used to populate the UITableView.

I am using SBJsonStreamParser and SBJsonStreamParserAdapter to parse the Json as it comes in.

I do have a working implementation (not shown), that basically calls the original code below each time a new object is received (running through the full loop of received objects each time). I want to know what is causing the error, though, not just get something working.

This is the original non-streaming code, called in 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];

And here is the code that crashes, called in parser:foundObject: The loop code has been removed, since this is called every time a new Json object is downloaded.

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

Finally, here is the actual part that crashes when using the second listing, in tableView:cellForRowAtIndexPath: By the way, it crashes when row == 1, not row == 0. For some reason row 0 is okay... It never has a chance to load the other rows, of course.

        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"];
        }
Foi útil?

Solução

The object invalidation most likely occurs when you call reset on the managedObjectContext immediately prior to running a fetch. Calling reset invalidates objects in memory but does not delete them until a save. If an invalidated managed object is retained by another object such as an array, then it will persist past the save in the invalidated form. When you run the fetch, the fetch returns the invalidated objects which cause the error when you try to access one of their attributes

reset is meant to be called when used with the undo manager. It is not a generic, "wipe the context" call. If you want to delete the existing objects, you need to fetch them and delete them explicitly.

Your code also has some other issues. You call release on the checkRequest array even though you didn't create it. This may cause the array to disappear at random. Likewise the listViewArray appears to be a class property but you never use the accessor forms e.g. self.listViewArray to ensure proper retention.

Outras dicas

Most likely cause is that the parser is not running on the main thread and you're using the same ManagedObjectContext across the main thread and the parser - you cannot do that - guaranteed to cause all sorts of strange behaviour.

You need to create a new ManagedObjectContext in the parser and associate that with the persistentStore of the main thread's ManagedObjectContext

The CoreData framework is quite clear on this - you cannot share ManagedObjectContext's across thread boundaries

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top