• iOS
  • 3 MINUTES READ

How to resolve Core Data background thread problem in iOS?

  • POSTED ON
  • May 7, 2014
  • POSTED BY
  • Aayan Arif
  • POSTED ON May 7, 2014
  • POSTED BY Aayan Arif

This article throws some light on working with Core Data background threads as it is not documented in any of Apple’s Core Data guide: Requirement and Idea: In one of our existing iPad application, we had to implement offline feature that requires storing all data in device’s local storage. We were using Apple’s Core Data,

Implementation-Notes-How-to-resolve-Core-Data-background-thread-problem-in-iOS

This article throws some light on working with Core Data background threads as it is not documented in any of Apple’s Core Data guide:

Requirement and Idea:

In one of our existing iPad application, we had to implement offline feature that requires storing all data in device’s local storage. We were using Apple’s Core Data, which was a more recommended way for persistent storage according to Apple’s documentation for OS X and iOS applications.

Precautions while using Core Data background:

Importing a large amount data can take considerable amount of time. If such operation is performed on main thread then UI becomes slow and unresponsive, resulting in a poor user experience. The answer to this problem involves multi-threading and breaking the job down into smaller pieces.

3rd Party libraries:

For our project, we were using a famous library Magical Records. The reason is that using Core Data requires writing considerable amount of code at start-up for fetching requests. Magical Records cleans up Core Data related code and allow for clear, simple, one-line data fetches.

Problem faced:

Although using Magical Records, we were able to fetch data objects in backgrounds, but as soon as we return to main thread and tried to update UI, the objects become “null” and no data is displayed to user. This was really tedious because we were fetching objects perfectly; debug Logs show all data as expected.

Problem reason:

After searching for more than one day and digging into MagicalRecord code, we finally found the cause of problem. For background fetches, the MagicalRecord library was creating a separate temporary NSManagedObjectContext that has a life cycle limited to background thread only. Data fetch request is performed using this temporary NSManagedObjectContext.

This was implemented in NSManagedObjectContext+MagicalThreading.m  file:

+ (NSManagedObjectContext *) MR_contextForCurrentThread;
{
if ([NSThreadisMainThread])
{
return [selfMR_defaultContext];
}
else
{
NSMutableDictionary *threadDict = [[NSThreadcurrentThread] threadDictionary];
NSManagedObjectContext *threadContext = [threadDict objectForKey:kMagicalRecordManagedObjectContextKey];

if (threadContext == nil)

{

threadContext = [
selfMR_contextWithParent:[NSManagedObjectContextMR_defaultContext]];

[threadDict
setObject:threadContext forKey:kMagicalRecordManagedObjectContextKey];

}

return threadContext;

}

}

As soon as we moved from background thread to main thread, the background thread was destroyed which caused temporary NSManagedObjectContext and all fetched data objects to become null. Hence no data is available in the main thread.

Solution:

We needed to re-fetch data from background thread to main thread before background thread is destroyed. Apple suggests the following way:

Using thread confinement, you should not pass managed objects or managed object contexts between threads. To “pass” a managed object from one context another across thread boundaries, you either:

  1. Pass its object ID (objectID) and use objectWithID: or existingObjectWithID:error: on the receiving managed object context.
  2. The corresponding managed objects must have been saved—you cannot pass the ID of a newly-inserted managed object to another context.
  3. Execute a fetch on the receiving context.

We went for 1st approach and copied all the data from background thread to main thread.

+ (void)backgroundFetchWithPredicate:(NSPredicate *)predicate completion:(void(^)(NSArray *, NSError *))completion {
NSManagedObjectContext *privateContext = [NSManagedObjectContextMR_context];

[privateContext
performBlock:^{

NSArray *privateObjects = [selfMR_findAllWithPredicate:predicate inContext:privateContext];

NSArray *privateObjectIDs = [privateObjects valueForKey:@”objectID”];

// Return to our main thread

dispatch_async(dispatch_get_main_queue(), ^{

NSPredicate *mainPredicate = [NSPredicatepredicateWithFormat:@”self IN %@”, privateObjectIDs];

NSArray *finalResults = [selfMR_findAllWithPredicate:mainPredicate];

completion(finalResults,
nil);

});

}];

}

The above method gets objectIDs of objects that were fetched in background thread, then using block gets a reference to main thread and create another array of objects *finalResults. This time array of objects (*finalResults) has been created in main thread and has been used to update UI successfully.

Conclusion:

Unfortunately, not all information was available at a single place, which could be used to perform this operation in a single go. After facing this problem, we finally concluded the following steps to work with Core Data in background threads:

  • Create/get an appropriate context for the background thread.
  • Create & make your  NSFetchRequest in that thread.
  • Get another appropriate context in the main thread.
  • In the main thread re-fetch the objects you just retrieved.

ABOUT THE AUTHOR

Aayan Arif

Content Strategist at vteams - Aayan has over 8 years of experience of working with multiple industries.

More Related Article
We provide tips and advice on delivering excellent customer service, engaging your customers, and building a customer-centric business.