Closed measuredweighed closed 10 years ago
So, this is not really a USM issue, but an important Core Data concept to get right, especially when using USM.
To understand how best to deal with these issues, it's important that we understand what exactly the scope is within which each type of relevant object is valid:
NSManagedObjectModel
: A model defines what entities exist in our store. It normally doesn't change in an application's lifetime. If you have special code that changes a model or merges different models, you should probably be invalidating your coordinator.NSPersistenceStoreCoordinator
: A coordinator controls the persistence of objects in a store. It usually survives unloading and reloading of different stores but this is no guarantee (USM may throw its coordinator away and make a new one when it deems it necessary). When the store comes up, always use the coordinator passed to the delegate to make your primary context.NSManagedObjectContext
: A context is a view on the objects within a store. This view doesn't necessarily represent what is actually in a store. Objects will be loaded from the store (or its parent context) into the context, can be modified, and finally saved back to the store (or back to its parent). A context's scope is two-fold: It's specific to one thread and it dies when USM unloads the active store.NSManagedObject
: An object is an instance of an entity in a specific context. Again, this isn't necessarily what is in the store, but rather what its context sees. An object's scope is pretty much identical to the scope of its context.NSManagedObjectID
: An identifier of an object. There are two types of identifiers: temporary and permanent identifiers. Temporary identifiers are identifiers of objects that have never been persisted before. These identifiers are only meaningful within the context of their object. All objects that have been persisted to the store at one point have a permanent identifier. While the object's scope is limited to its context, the object's permanent identifier has a much broader scope: it's valid in all contexts of the store.So what does that mean for passing these things around?
Here's what I'd recommend:
coordinator
. When you get it in didLoadStoreForCoordinator:coordinator
, use it to make your primary context and then forget about it.context
with an instance variable in your application's delegate. Your primary context should be created in didLoadStoreForCoordinator:
using the coordinator
you are given. I'd recommend making my primary context using NSPrivateQueueConcurrencyType
, but simpler applications may prefer NSMainQueueConcurrencyType
. Be certain that private contexts are always accessed using their performBlock
methods and that main contexts are only accessed from the methods running in the main thread. If your primary context is private, you'll either want to create a child main context or create a new main context each time a UI event occurs. Creating contexts is cheap: you can do this whenever you need to do something new without too much of a performance worry. '''Be sure to unset''' all your contexts in willLoadStoreIsCloud:
. You may want to first save your primary context, if you don't do so explicitly whenever you change it. nil
your context instance variables and make sure your UI doesn't crash or bug out when you have no context. Some prefer to show an activity indicator of sorts so the user knows the UI is temporarily unavailable.Your NSManagedObject
s should be only local variables. Make sure not to pass them between threads (so be careful about passing them into blocks!). If you're going to pass them as arguments to method calls, make sure there's a clear contract between these methods about what context to use when working with the object. Keep in mind that object.context
may be nil
, so you can't always rely on getting the context that created the object from there. Also keep in mind that it's clear who's going to be saving the context after the object's been modified. Personally, I add this information to my method names (inContext
vs saveInContext
):
[delegate signInAsUser:[self selectedUserInContext:context] saveInContext:context
usingMasterPassword:self.passwordField.text];
When you want to pass objects between threads or you want to hold onto them (eg. "the active user"), you should use NSManagedObjectID
s. More specifically, you should use '''permanent''' object ids (if you want to make sure whether your object has a permanent object.objectID
, you could test its .isTemporaryID
. To get a permanent ID, you'll need to save the object. You may also look into [context obtainPermanentIDsForObjects:@[ object ] error:&error
). When you want to get the object for your ID, you can use your context's existingObjectWithID:objectID error:&error
. When storing IDs, I tend to use the following pattern: I have a private instance variable for the objectID, a getter method for the object which takes a context and a setter method:
- (MPUserEntity *)selectedUserInContext:(NSManagedObjectContext *)context {
if (!_selectedUserOID)
return nil;
NSError *error;
MPUserEntity *selectedUser = (MPUserEntity *)[context existingObjectWithID:_selectedUserOID error:&error];
if (!selectedUser) {
err(@"Failed to retrieve selected user: %@", error);
_selectedUserOID = nil;
}
return selectedUser;
}
- (BOOL)setSelectedUser:(MPUserEntity *)selectedUser {
if ([_selectedUserOID isEqual:selectedUser.objectID])
return NO;
NSError *error = nil;
if (selectedUser.objectID.isTemporaryID &&
![selectedUser.managedObjectContext obtainPermanentIDsForObjects:@[ selectedUser ] error:&error]) {
err(@"Failed to obtain a permanent object ID after setting selected user: %@", error);
_selectedUserOID = nil;
}
_selectedUserOID = selectedUser.objectID;
return YES;
}
Hopefully this makes things a bit clear for you. Let me know if you have any comments or need more details.
I'm currently looking to implement iCloud sync in an existing application that has, up until this point, passed around an instance of my main NSManagedObjectContext to various controllers and models.
Given that the UbiquityStoreManager documentation warns that my main MOC could become unavailable at any time, I've taken steps to instead refer to my AppDelegate's MOC property and to fail gracefully if it is unavailable. That seems clear, however I'm confused as to best practice when referencing NSManagedObjects.
As a basic example: I pass NSManagedObject references to my detail view controllers for editing purposes. I'm aware that it's generally a bad idea to shift NSManagedObjects between NSManagedObjectContexts, so what's the best way of handling this? Should I be looking up the NSManagedObject via it's objectID in the current main MOC each time I try to access it within the detail view?