Skip to content

Core Data in the project

Balázs Németh edited this page Feb 22, 2015 · 18 revisions

Introduction

This page is going to describe some CoreData rules and practices. They are universal but the examples are slightly project specific: they are originated from the context of the project source code to be more descriptive.

I tried to keep is short, but it's a complex topic, so fill free to read only the relevant topic for you.

CoreData in the Background

Never passing managed objects between threads.

It's an old rule, but sometimes it is not clarified enough. This doesn’t just mean that you should never modify a managed object on another thread, but also that you should never read any properties from it. To pass around an object, pass its object ID and retrieve the object from the context associated to the other thread. objc.io - Common Background Practices

Example service of UserProfileManager: Wrong way: Pass the loggedInUser MO to get the objectID. Right way: used loggedInUserMOID NSManagedObjectID to get the object from the context.

bad example how you pass around a MOC from different thread

The right example is written to get the MOC as a dependency, it is a more clean helper approach.

-(BOOL)isThisMemberTheLoggedInUser:(CDMember *)member onContext:(NSManagedObjectContext *)context{
    NSAssert(member,@"Member must be specified to check it is the logged in user!");
    NSAssert(member.managedObjectContext == context,@"'member' (MO) must belong to the 'context' (MOC)!");
    
    //To keep it simple the error handling is ignored, but it is still mandatory on production code!
    CDMember *localLoggedInUser = (CDMember *)[context existingObjectWithID:self.loggedInUserMOID error:nil];
    
    if (localLoggedInUser && [member.memberID isEqualToNumber:localLoggedInUser.memberID] ){
        return YES;
    }
    
    return NO;
}

Make sure you save the object into the store before getting the object ID. Until saved, they're temporary, and you can't access them from another thread. Or obtain permanant IDs for the new objects using the obtainPermanentIDsForObjects:error: service of MOC.

RestKit is going to obtain permanant IDs in the saveToPersistentStore: function. That's the reason why we can use the obejctID-s safely in case of the MO mapped and persisted by RestKit.

Concurrency type of MOC

iOS5 and above you should initialize managed object contexts with either the NSPrivateQueueConcurrencyType or the NSMainQueueConcurrencyType. The third concurrency type constant (NSConfinementConcurrencyType) is for legacy code, it is not recommended to use. If a context with the queue-based concurrency types are chosen to use, than it's necessary to do anything that touches it or objects that belong to it within performBlock: or performBlockAndWait:. CoreData Release Notes for iOS5

performBlock: and performBlockAndWait:

performBlock: and performBlockAndWait: ensure the block operations are executed on the queue specified for the context. It is not required anymore to specify the main or a background queue for the MOC manually, the MOC has an internal queue corresponding it's concurrency type.

  • performBlockAndWait will always run in the calling thread (but will be appropriately synchronized with the other blocks submitted to the MOC's queue). It is not creating separated queue with a new autorelesepool, it does not use the internal queue of the MOC even if it specified as NSPrivateQueueConcurrencyType type. Furthermore, it is re-entrant, so nested calls all happen right in that calling thread. But it is safe. So, assume that your code is running on a background thread. You would like to modify something on a MO. Than you can get a new child MOC from RestKit, than call the performBlockAndWait: of this MOC and perform the modifications. The modifications will be performed on the original background thread but in safe.

The performBlockAndWait: should rarely be used, and then only if you have a really good reason (i.e., it does not work otherwise) - from my experiences, that's quite rare.

  • performBlock is completely asynchronous. It will always enqueue the block onto the queue of the receiving MOC, and the block is wrapped within their own autorelease pool.

For MOC with NSPrivateQueueConcurrencyType or NSMainQueueConcurrencyType :

The contexts using the queue-based concurrency types must be used in conjunction with two new methods: performBlock: and performBlockAndWait:. It's necessary to do anything that touches it or objects that belong to it within a block to pass to one of these methods (including initialization such as setting the persistent store coordinator and so on). The one exception is: if your code is executing on the main thread, you can invoke methods on the main queue style contexts directly instead of using the block based API. CoreData Release Notes for iOS5

An example for background operation (without any manual dispatching):

NSMangedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
temporaryContext.parentContext = mainMOC;
 
[temporaryContext performBlock:^{
   // do something that takes some time asynchronously using the temp context
 
   // push to parent
   NSError *error;
   if (![temporaryContext save:&error])
   {
      // handle error
   }
 
   // save parent to disk asynchronously
   [mainMOC performBlock:^{
      NSError *error;
      if (![mainMOC save:&error])
      {
         // handle error
      }
   }];
}];

In the above example the lengthy operation is performed on a background queue. Once this is done and the changes are pushed to the parent via saveContext then there is also an asynchronous performBlock for saving the mainMOC. This again is happening on the correct queue as enforced by performBlock. Multi context Core Data

##### Use only performBlockAndWait: in the handler of NSManagedObjectContextDidSaveNotification:

Apple's docs for NSManagedObjectContextDidSaveNotification state:

"You can only use the managed objects in this notification on the same thread on which it was posted."

Therefore, performBlock: should not be used to handle these notifications, but performBlockAndWait:. Nested context concurrency issues

A note about managedObjectRequestOperationWithRequest:request: service of RestKit

The following service of RestKit is widely used in the project:

[[RKObjectManager sharedManager] managedObjectRequestOperationWithRequest:request
                                                         managedObjectContext:managedObjectContext
                                                                      success: ^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
                                                                          //Get the mapped object and call completion block
                                                                      }
                                                                      failure:^(RKObjectRequestOperation *operation, NSError *error) {
                                                                          //Call completion block with error
                                                                      }]; 

It is important to know, that the managedObjectContext input parameter does not specify the MOC on which the operation will be performed! Rather than this MOC will be used as the parent context of a new operation local NSManagedObjectContext with the NSPrivateQueueConcurrencyType concurrency type. Upon success, the private context will be saved and changes resulting from the object mapping will be 'pushed' to the given context. RKObjectManager

Due to the role of RestKit (in the project) is to cache the server responses, and the mapped objects are always used on the main thread to update the UI (by fetchedResultsController or by handling a CD notification), there is no reason to specify a new child context as 'managedObjectContext' instead of the main context.

How to debug CD concurrency

The GDCoreDataConcurrencyDebugging framework is really useful to debug concurrency issues. On the coredata-concurrency-debugging GIT branch there are some instructions, examples and a short description of the methodology how is efficient debugging CD in the project. Check this out when you have a mysterious bug.

And the following chapter is really mandatory to debug the CD issues successfully: Troubleshooting Core Data (by Apple)

##Good to read