dizy
dizy

Reputation: 7959

Getting EXC_BAD_ACCESS when using dispatch_async with Core Data

I have images in coredata which I'm trying to load lazily for a table view. Each cell uses an observer for the related core data entity to update the image when one becomes available. The relevant code in the entity is as follows:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  // The heavy lifting seems to be during firing of the fault and accessing data,
  // so i'm trying to do that in the background thread.
  UIImage *i = [UIImage imageWithData:self.imageEntity.data];
  // I now need to notify observers that the image is ready on the main thread
  dispatch_async(dispatch_get_main_queue(), ^{
    [self willChangeValueForKey:@"image"];
    image = i;
    [self didChangeValueForKey:@"image"];
  });
});

The project uses ARC, I'm not getting any compiler errors or warnings, and when I run it kind of works until I scroll fast, and then I get a EXC_BAD_ACCESS on the line when i'm declaring the i.

What am I missing here?

Upvotes: 5

Views: 6570

Answers (3)

dizy
dizy

Reputation: 7959

Apparently fetching CoreData objects is not thread safe. So it's suggested to use the same persistentStoreCoordinator, but different ObjectContexts. Here's my updated code that no longer seems to crash:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  @autoreleasepool {
    // Create a new context
    NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc] init];
    // Use an existing coordinator
    NSPersistentStoreCoordinator *coordinator = [[DataSource sharedDataSource] persistentStoreCoordinator];
    [backgroundContext setPersistentStoreCoordinator:coordinator];
    // Getting objectID does not fire the fault, so we grab it but actually fetch the object
    // on a background context to be thread safe.
    Image *imageEntity = (Image*)[backgroundContext objectWithID:self.imageEntity.objectID];
    image = [UIImage imageWithData:imageEntity.data];
    // Notify observers that the image is ready on the main thread
    dispatch_async(dispatch_get_main_queue(), ^{
      [self willChangeValueForKey:@"image"];
      [self didChangeValueForKey:@"image"];
    });
  }
});

Upvotes: 7

bontoJR
bontoJR

Reputation: 7055

CoreData is not thread safe, you've to manage contexts to avoid crashes. If you plan to heavy use a lot of concurrent processes to update data in Core Data, I would suggest you to take a look to MagicalRecord, an amazing pattern inspired by Active Record of Rails and that handles all these aspects in a really smart way.

Upvotes: 0

Jack Cox
Jack Cox

Reputation: 3300

Dizy, also keep in mind that the Image object that is created in the code:

UIImage *i = [UIImage imageWithData:self.imageEntity.data];

is set for autorelease. The dispatch_async method runs on the main queue, so there is a chance that the memory allocated for the image may be released by the time the main thread runs the dispatch block.

Upvotes: 1

Related Questions