Andrew
Andrew

Reputation: 16051

My code, when using core data, is crashing with 'index beyond bounds' error

I'm not sure how to solve this problem. This code runs in each cell every time the user scrolls and shows it in a UITableView:

self.isFinishedProcessing = NO;

    [self setNeedsDisplay];

    [self.mediaArray removeAllObjects];
    self.mediaArray = [[NSMutableArray alloc] init];

    dispatch_queue_t queue = dispatch_queue_create("setup_cell", NULL);

    NSManagedObjectID *objectID = [self.entry objectID];
    dispatch_async(queue, ^{

        CoreDataStore *customStore = [CoreDataStore createStore];

        Entry *entry = (Entry *)[customStore.context objectWithID:objectID];

        if (self.cellInfo.numberOfMediaItems > 0) {

            int i = 0;

            int numberOfThumbnails = MIN(self.cellInfo.numberOfMediaItems, 3);

            while (i < numberOfThumbnails) {
                Media *media = [entry.media objectAtIndex:i];

                UIImage *image = [media getThumbnail];
                [self.mediaArray addObject:image];

                i++;
            }
        }

        dispatch_async(dispatch_get_main_queue(), ^{

            self.isFinishedProcessing = YES;
            [self setNeedsDisplay];
        });

    });

The core data store takes the Entry, which is a core data class, along with Media, and puts it in it's own context.

I haven't figured out exactly when this code crashes when scrolling, but it happens when scrolling up and down a few times.

EDIT: NSLog before 'object at index' says there is a count of 3. I actually did entry.media.count for that, to be sure.

Here's the error in full:

*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 1 beyond bounds [0 .. 0]'

Edit 2:

Still haven't solved this problem. I've added this on the end:

        if (self.mediaArray.count != self.entry.media.count) {
        NSLog(@"INCORRECT BECAUSE.... media array count: %i, entry media count: %i number of media items: %i", self.mediaArray.count, self.entry.media.count, self.cellInfo.numberOfMediaItems);
        }

And it often crashes after a cell with something like this:

INCORRECT BECAUSE.... media array count: 1, entry media count: 3 number of media items: 3

Not sure how the media array count could possibly be wrong, if it's created relying on self.cellInfo.numberOfMediaItems.

Also worth noting that this only happens when done in a separate thread. Never in the main thread does it crash.

Upvotes: 2

Views: 799

Answers (1)

Jody Hagins
Jody Hagins

Reputation: 28349

You are violating Rule #1 of Core Data with multiple threads.

You must not use a MOC in a thread unless it was created in that thread.

You create a queue to run some work for you...

dispatch_queue_t queue = dispatch_queue_create("setup_cell", NULL);

and then in that block, you are using a MOC not created there...

Entry *entry = (Entry *)[customStore.context objectWithID:objectID];

If you want to do work with a MOC, you must follow the rules. I will summarize Rule #1 because it has several clauses.

  • If you create your MOC with NSConfinementConcurrencyType, you can not call performBlock on that context. You must use it only within the thread that created it.

  • If you use NSMainQueueConcurrencyType, you must access the MOC from within the main thread, or via [moc performBlock].

  • If you use NSPrivateQueueConcurrencyType, you must use it only via [moc performBlock].

EDIT

Your items are out of sync. You should only count on what the data actually tells you. Obviously, you are counting on the count from self.cellInfo.numberOfMediaItems but it is not in sync with your actual data.

You must be very careful when updating data from several threads. Your code has "lost track" of what is actually happening. Probably because you have not yet saved stuff into the context from which you are pulling data.

Read the docs on Core Data an concurrency. Make sure you are not changing anything in another thread without notifying all the other contexts. You should be using parent/child contexts and/or handling DidSave notifications to keep your data in sync.

Upvotes: 6

Related Questions