-
Notifications
You must be signed in to change notification settings - Fork 3
Core Data in the project
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.
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.
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.
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:
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 asNSPrivateQueueConcurrencyType
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 newchild MOC
from RestKit, than call theperformBlockAndWait:
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
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.
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
- objc.io - Common Background Practices
-
Multi context Core Data - The third way (described in the
Asynchronous Saving
chapter) is the same as RestKit MOC hierarchy is built - see here the Managed Object Context paragraph. - RKObjectManager
- WWDC 2011 Session 303 (What's New in Core Data on iOS)