I'm passing some NSManagedObject data between two threads using NSOperationQueue with concurrency level to max of 1 and I'd like some suggestions on whether I'm doing this correctly.
Since NSManagedObject is not thread-safe, I'm sending in the NSManagedObjectID from ThreadA (main thread) to ThreadB via an NSOperation derived class. The general work flow:
ThreadA (main thread):
creates NSPersistentStoreCoordinator
creates main NSManagedObjectContext(1)
creates NSManagedObjectContext(2) for use in workerThread
creates MyNSOperationItem, passes along NSManagedObjectContext and adds MyNSOperationItem to NSOperationQueue
ThreadB (NSOperationQueue's thread):
NSOperation derived class will retrieve data from the persistent
store using the supplied objectID.
My NSOperation class looks like this:
#interface MyNSOperationItem: NSOperation
{
// MyNSOperationItem is created in thread1 and MOC will be
// set on creation
NSManagedObjectContext *threadedMOC;
NSManagedObjectID *workItemObjectID;
}
#end
So is it okay for my NSOperation derived class to have a reference to NSManagedObjectContext or should I store the second NSManagedObjectContext elsewhere? Since this is a queue, numerous instances of MyNSOperationItem will have been created, each of them pointing to the same NSManagedObjectContext.
I think this should give you all you need:
http://developer.apple.com/mac/libra...reData/Articles/cdMultiThreading.html
If you need to offload the fetch to a background thread, here is a little tip from the document I referenced above:
Fetching in a Background Thread
One of the simplest multi-threading
techniques you can use with Core Data
to improve application responsiveness
is to execute a fetch request on a
background thread. (Note that this
technique is only useful if you are
using an SQLite store, since data from
binary and XML stores is read into
memory immediately on open.) This
means that if a fetch is complicated
or returns a large amount of data, you
can return control to the user and
display results as they arrive. For an
example of how to do this, see the
BackgroundFetching example in
/Developer/Examples/CoreData/.
You use two managed object contexts
associated with a single persistent
store coordinator. You fetch in one
managed object context on a background
thread, and pass the object IDs of the
fetched objects to another thread. In
the second thread (typically the
application's main thread, so that you
can then display the results), you use
the second context to fault in objects
with those object IDs (you use
objectWithID: to instantiate the
object).
Related
I want to change core data objects on the main thread and then save in a background thread. Would the background thread save include the main thread changes?
You can use Core Data in a multithread fashion, but you should follow one of the approaches recommended by Apple:
The pattern recommended for concurrent programming with Core Data is thread confinement: each thread must have its own entirely private managed object context.
There are two possible ways to adopt the pattern:
Create a separate managed object context for each thread and share a single persistent store coordinator.
This is the typically-recommended approach.
Create a separate managed object context and persistent store coordinator for each thread.
This approach provides for greater concurrency at the expense of greater complexity (particularly if you need to communicate changes between different contexts) and increased memory usage.
Particularly:
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:
Pass its object ID (objectID) and use objectWithID: or existingObjectWithID:error: on the receiving managed object context.
The corresponding managed objects must have been saved—you cannot pass the ID of a newly-inserted managed object to another context.
Execute a fetch on the receiving context.
These create a local version of the managed object in the receiving context.
From this, it ensues that you cannot create managed objects in a thread (with its own context) then save them in the other.
So, in order to accomplish what you would like to, you need to share a managed object context or a persistent store coordinator between threads. In this case you should properly use locking techniques to prevent your store from entering an inconsistent state:
If you share a managed object context or a persistent store coordinator between threads, you must ensure that any method invocations are made from a thread-safe scope. For locking, you should use the NSLocking methods on managed object context and persistent store coordinator instead of implementing your own mutexes. These methods help provide contextual information to the framework about the application's intent—that is, in addition to providing a mutex, they help scope clusters of operations.
I would not suggest to go this route, if you have any chance to modify the design of your app so that you do not need going into locking and synchronization for your core data object.
Please refer following URLs,
1) CORE DATA AND THREADS, WITHOUT THE HEADACHE:
2) Concurrency with Core Data:
3) Multi-Context CoreData:
What you are asking for fits right in with nested contexts. You create a private-queue context, and attach it directly to the persistent-store-coordinator. It is simple. Take your current code, and, instead of this...
managedObjectContext = [[NSManagedObjectContext alloc] init];
replace it with this...
workerManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
Now, you have replaced your traditional confinement MOC with a new MOC that will run with its own concurrency queue.
To get your context that you can use from the main thread, you create another managed object context, and make it a child of the one you just created...
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
managedObjectContext.parent = workerManagedObjectContext;
This means that you can use managedObjectContext just like you were before. Now, however, instead of it going directly to the store, it goes through an intermediary context. The parent context will perform work in its own background thread.
Thus, you can make all the changes to managedObjectContext that you want. When it comes time to save, you do something like this...
static void saveManagedObjectContext(NSManagedObjectContext *moc, void(^completionBlock)(NSError *error)) {
[moc performBlock:^{
NSError *error = nil;
if (moc.hasChanges && [moc save:&error] && moc.parentContext) {
saveManagedObjectContext(moc.parentContext, completionBlock);
} else {
completionBlock(error);
}
}];
}
EDIT
If you want to use this universally, you can easily add it to a category on NSManagedObjectContext, and then just call...
[managedObjectContext saveWithCompletionBlock:^(NSError *error){
if (error) {
// Handle the error
return;
}
// Handle success...
}];
Apple recommends to use separate contexts for separate threads. Once you call a save it's the context state that gets saved to actual database. If you need your main thread changes to be reflected in background thread, merge background thread context with main thread context and call save.
I have three contexts:
masterMOC - private queue tied to the persistent store, so physical saves happen here
----mainMOC - main queue tied to the UI, child of masterMOC
-------backgroundMOC - private queue, child of mainMOC
Let's see I create an Employee object on the mainMOC, then save the mainMOC. Then I save the masterMOC (which writes to disk).
Now, I've saved the Employee NSManagedObjectID in a variable, objectID. I want to get this Employee on the backgroundMOC. Does [backgroundMOC objectWithId:objectID] serve this purpose? Will it go to the persistent store and fetch this object using that method? Or will I have to preform a fetch request?
Your'e doing it right. They want you to pass objects between MOContexts using ID's. objectWithId will hit the persistent store and load the object in a fresh state.
The only gotcha you have to worry about is this case.
You fetch an object or create a new object in a MOContext.
You try pass the objectID to another context WITHOUT SAVING
The new MOContext wont know about the updates, and if you created a new object the objectID wouldn't be in the persistent store, so I think it returns nil or it's not defined.
There is a WWDC video from this year titled 'Core Data Best Practices' that talks about nested MOC's. But to answer your question, yes, objectWithId will travel up through the fewest number of MOC levels to find the object. So if you call [backgroundMOC objectWithId:objectID] and the object exists in the mainMOC, it will get it from the mainMOC without having to go all the way to the masterMOC or the database.
I'm trying to import large amout of data according to this solution. It is suggested that each importing threads should have its own managedObjectContext and they cannot pass any managedObject among them. So, my question is how we can set the relationship if two objects was created in different MOCs?
PS: After saving MOC in thread, it notifies the main MOC to merge the contexts. So related object for new object is always located in main MOC. In other words related object has already been created.
You can't pass NSManagedObjects between threads, but you can pass NSManagedObjectIDs.
Pass those over, and then retrieve the objects themselves from the context that you want to set the relationship within.
Can anybody give me example that NSMutableArray is thread safe or not?
It is not thread safe. See the list of thread safe/unsafe classes here
According to the Apple docs NSMutableArray is not thread safe.
Mutable objects are generally not
thread-safe. To use mutable objects in
a threaded application, the
application must synchronize access to
them using locks. (For more
information, see “Atomic Operations”).
In general, the collection classes
(for example, NSMutableArray,
NSMutableDictionary) are not
thread-safe when mutations are
concerned. That is, if one or more
threads are changing the same array,
problems can occur. You must lock
around spots where reads and writes
occur to assure thread safety.
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html
How can I detect changes to a specific attribute of an NSManagedObject? In my Core Data data model, I have a Product entity that represents a product for sale. The Product entity has several attributes: price, sku, weight, numberInStock, etc. Whenever the price attribute of a Product changes, I need to perform a lengthy calculation. Consequently, I would like to know when the price attribute of any Product changes, [edit] even if that change comes from merging a context saved on another thread. What is a good way to go about doing this? I have thousands of Product objects in my store; obviously it's not feasible to send each one an addObserver message.
I have been using NSManagedObjectContextObjectsDidChangeNotification to detect changes, but it only notifies me that a managed object has changed, not which attribute of that object has changed. I could redo the calculation whenever there's any change to a Product, but that results in useless recalculations whenever an irrelevant attribute has changed. I'm considering making a Price entity (that only contains a price attribute) and using a to-one relationship between Product and Price. This way, I can detect changes to Price objects in order to kick off the calculation. This seems excessively kludgy to me. Is there a better way?
Update:
#railwayparade pointed out that I could use the changedValues method of NSManagedObject to determine which properties have changed for each updated object. I completely missed that method, and it would totally solve my problem if the changes weren't being made on a background thread and merged into the main thread's context. (See next paragraph.)
I completely missed a subtlety about the way that NSManagedObjectContextObjectsDidChangeNotification works. As far as I can tell, when a managed object context saved on another thread is merged into a context on the main thread (using a mergeChangesFromContextDidSaveNotification:), the resulting NSManagedObjectContextObjectsDidChangeNotification only contains change information about objects that are currently in the main thread's managed object context. If a changed object isn't in the main thread's context, it won't be part of the notification. It makes sense, but wasn't what I was anticipating. Therefore, my thought of using a to-one relationship instead of an attribute in order to get more detailed change information actually requires examination of the background thread's NSManagedObjectContextDidSaveNotification, not the main thread's NSManagedObjectContextObjectsDidChangeNotification. Of course, it would be much smarter to simply use the changedValues method of NSManagedObject as #railwayparade helpfully pointed out. However, I'm still left with the problem that the change notification from the merge on the main thread won't necessarily contain all of the changes made on the background thread.
One point with regard to this thread,
The NSManagedObjectContextObjectsDidChangeNotification generated by Core Data indicates that a managed object has changed, but doesn't indicate which attribute has changed.
It actually does. The "changedValues" method can be used to query which attributes changed.
Something like,
if([updatedObjects containsKindOfClass:[Config class]]){
//if the config.timeInterval changed
NSManagedObject *obj = [updatedObjects anyObject];
NSDictionary *dict=[obj changedValues];
NSLog(#"%#",dict);
if([dict objectForKey:#"timeInterval"]!=nil){
[self renderTimers];
}
}
This type of circumstance is where you need a custom NSManagedObject subclass. You need the subclass because you are adding a behavior, reacting to a price change, to the managed object.
In this case, you would override the accessor for the price attribute. Create a custom subclass using the popup menu in the data model editor. Then select the price attribute and choose 'Copy Obj-C 2.0 Implementation to the Clipboard`. It will give you a lot of stuff but the key bit will look like this:
- (void)setPrice:(NSNumber *)value
{
[self willChangeValueForKey:#"price"];
[self setPrimitivePrice:value];
[self didChangeValueForKey:#"price"];
}
Just add the code to deal with the price change and you are done. Anytime a specific product's price changes, the code will run.
You could take a look at KVO (Key Value Observing). Not sure if there are wrappers built into Core Data API, but I know it's part of Objective-C.
I thought I would document my design decisions here in case they're useful to others. My final solution was based on TechZen's answer.
First, I'll start with a short, and hopefully clearer, restatement of the problem:
In my application, I want to detect changes to a specific attribute (price) of a managed object (Product). Furthermore, I want to know about those changes whether they're made on the main or a background thread. Finally, I want to know about those changes even if the main thread currently does not have the changed Product object in its managed object context.
The NSManagedObjectContextObjectsDidChangeNotification generated by Core Data indicates that a managed object has changed, but doesn't indicate which attribute has changed. My kludgy solution was to create a Price managed object containing a single price attribute, and to replace the price attribute in Product with a to-one relationship to a Price managed object. Now, whenever a change is made to a Price managed object, the Core Data NSManagedObjectContextObjectsDidChangeNotification will contain that Price object in its NSUpdatedObjectsKey set. I simply need to get this information to the main thread. This all sounds good, but there's a hitch.
My Core Data store is being manipulated by two threads. This is done in the "usual" way—there is a managed object context for each thread and a single shared persistent store coordinator. After the background thread makes changes, it saves its context. The main thread detects the context save via the NSManagedObjectContextDidSaveNotification and merges the context changes using mergeChangesFromContextDidSaveNotification:. (Actually, since notifications are received in the same thread they're posted in, the NSManagedObjectContextDidSaveNotification is received on the background thread and passed to the main thread via performSelectorOnMainThread: for merging.) As a result of the merge, Core Data generates a NSManagedObjectContextObjectsDidChangeNotification indicating the changed objects. However, as far as I can tell, the NSManagedObjectContextObjectsDidChangeNotification only includes those objects which are currently represented in the receiving context. This makes sense from the perspective of updating the UI. If a managed object isn't being displayed, it probably won't be in the context, so there's no need to include it in the notification.
In my case, my main thread needs to know about changes made to managed objects whether or not they're currently in the main thread's context. If any price changes, the main thread needs to queue an operation to process that price change. Therefore, the main thread needs to know about all price changes even if those changes are made on a background thread to a product that's not currently being accessed on the main thread. Obviously, since NSManagedObjectContextObjectsDidChangeNotification only contains information about objects currently in the main thread's context, it doesn't meet my needs.
The second option I thought of was to use the NSManagedObjectContextDidSaveNotification generated by the background thread when it saves its context. This notification contains information about all changes to managed objects. I already detect this notification and pass it to the main thread for merging, so why not peek inside and see all of the managed objects that have changed? You'll recall that managed objects are not meant to be shared across threads. Consequently, if I start examining the contents of NSManagedObjectContextDidSaveNotification on the main thread, I get crashes. Hmm ... so how does mergeChangesFromContextDidSaveNotification: do it? Apparently, mergeChangesFromContextDidSaveNotification: is specifically designed to work around the "don't share managed objects across threads" restriction.
The third option I thought of was to register for NSManagedObjectContextDidSaveNotification on the background thread and while still on the background thread convert its contents into a special PriceChangeNotification containing object IDs instead of managed objects. On the main thread, I could convert the object IDs back into managed objects. This approach would still require the to-one Price relationship so that changes in prices are reflected as changes to Price managed objects.
I based my fourth option on TechZen's suggestion to override the price setter in the Product managed object. Rather than use a to-one relationship to force Core Data to generate the notifications I needed, I went back to using a price attribute. In my setPrice method, I post a custom PriceChangeNotification. This notification is received on the background thread and is used to construct a set of Product objects with price changes. After the background thread saves its context, it posts a custom PricesDidChangeNotification which includes the object IDs of all Product objects whose prices have changed. This notification can be safely transferred to the main thread and examined because it uses object IDs instead of managed objects themselves. On the main thread I can fetch the Product objects referenced by those object IDs and queue an operation to perform the lengthy "price change" calculation on a new background thread.
Are you using an NSArrayController or some other controller? Presumably you need some way for the user to interact with the model. It's this point of interaction that give a nice hook for this type of update call. Perhaps the appropriate strategy is to observe the relevant properties of the array controller's arrangedObjects.