Why does RestKit save all NSManagedObjectContexts via performBlockAndWait? - ios

I noticed that when saving NSManagedObjectContexts, RestKit wraps the save call on each NSManagedObjectContext with a performBlockAndWait.
https://github.com/RestKit/RestKit/blob/development/Code/CoreData/NSManagedObjectContext%2BRKAdditions.m#L64
It was my understanding of managing parent and child NSManagedObjectContexts that only a NSManagedObjectContext with type MainQueueConcurrencyType should be saved this way (and that is usually the child context of another NSManagedObjectContext of type PrivateQueueConcurrencyType which is what is actually associated with persistentStoreCoordinator). I thought that the idea was that saving to the persistent store (ie disk) is a longer operation and doesn't, and shouldn't, be waited for. Where am I going wrong?

Everything you do with a ManagedObjectContext has to be done on that context's dispatch queue. The easiest way to make sure that happens is to do it in a block called by performBlock or performBlockAndWait. If there is code later in the method depending on the result of the block, performBlockAndWait is the way to go. If you have to add these blocks to older Core Data code, as in the case of RestKit, wrapping NSManagedObjectContext calls in a performBlockAndWait is a not-too-painful way to make your Core Data code (more) thread-safe.

Related

Which is the right MOC parameter for RestKit's managedObjectRequestOperationWithRequest: service

I used RestKit integrated with CoreData in my project. To perform the network requests and map its JSON response the following service is widely used in the project:
- (RKManagedObjectRequestOperation *)managedObjectRequestOperationWithRequest:(NSURLRequest *)request
managedObjectContext:(NSManagedObjectContext *)managedObjectContext
success:(void (^)(RKObjectRequestOperation *operation, RKMappingResult *mappingResult))success
failure:(void (^)(RKObjectRequestOperation *operation, NSError *error))failure
The documentation of this function has this statement for the managedObjectContext attribute:
The managed object context with which to associate the operation. This
context 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.
Is it any reason to use another MOC instead of the main MOC for this request? I can only imagine one case, when a new private context should be used: If at some point the user is able to drop the mapped objects by the given network call. At this case it could be reasonable to keep the newly mapped objects on a separated context, and the changes will be propagated to the main context only if the user make the corresponding action, otherwise the context will be dropped with all its changes.
In my case, the RestKit is used for cache. After a request was performed and the response has mapped, than a fetchedRestultsController's delegate will be called by CD to update the UI. As a result I always specify the mainContext as the managedObjectContext attribute.
In an example project somewhere I found, that this attribute is specified to the MOC of the called thread. If it was the main thread, than the main context will be the input, if it was started from a background thread, than a new child MOC was created for the request. But I think, it is not reasonable, right?
Your question isn't really about RestKit or this method as such, it's about Core Data thread confinement. You have a responsibility to only use a managed object context from the designated thread, and if you ask an API to do something on a context it is again your responsibility to pass the correct one for the thread. So, if you're making a main thread request you should pass the main thread context. RestKit will always create a child context because it creates a background thread to do the heavy lifting.
It is possible, though uncommon, that you would start from a background thread and then you need to be careful about run loops, callback queues and notifications. It can get complicated quickly so this is usually best avoided...
It's generally easier to use the object manager instead of the operations directly as it deals with these choices for you. You're also almost always starting on the main thread and allowing RestKit to handle the background download and mapping for you, then pushing the result back to main.

Requesting Scalar Values from main thread managedobjectcontext from background thread

I have a method that runs in a background thread using a copy of NSManagedObjectContext which is specially generated when the background thread starts as per Apples recommendations.
In this method it makes a call to shared instance of a class, this shared instance is used for managing property values.
The shared instance that managed properties uses a NSManagedObjectContext on the main thread, now even though the background thread method should not use the NSManagedObjectContext on the main thread, it shouldn't really matter if the shared property manager class does or does not use the such a context as it only returns scalar values back to the background thread (at least that's my understanding).
So, why does the shared property class hang when retrieving values via the main threads context when called from the background thread? It doesn't need to pass an NSManagedObject or even update one so I cannot see what difference it would make.
I can appreciate that my approach is probably wrong but I want to understand at a base level why this is. At the moment I cannot understand this whole system enough to be able to think beyond Apples recommended methods of implementation and that's just a black magic approach which I don't like.
Any help is greatly appreciated.
Does using:
[theContext performBlock:^{
// do stuff on the context's queue, launch asynchronously
}];
-- or --
[theContext performBlockAndWait:^{
// do stuff on the context's queue, run synchronously
}];
-- just work for you? If so, you're done.
If not, take a long, hard look at how your contexts are setup, being passed around, and used. If they all share a root context, you should be able to "move" data between them easily, so long as you lookup any objectIDs always on your current context.
Contexts are bound to threads/queues, basically, so always use a given context as a a reference for where to do work. performBlock: is one way to do this.

Can you use an NSManagedObject outside of it's context's performBlock?

NSManagedObjectContext's have had the performBlock: and performBlockAndWait: methods added to help make concurrency easier. I've been using them -- potentially fairly naively -- and I just realized that there's a question I've never really asked.
If I create an NSManagedObject subclass inside one of the performBlock methods, it's 'home' thread is the thread of it's parent context -- which in the case of the NSPrivateQueueConcurrencyType is probably an independent thread I have no other access to.
So do I need to do a performBlock call just to access the data contained inside my managed objects? Or is there a background magic going on to help protect me in the case of using getters? (Or setters, though that seems like a bad idea...)
NSManagedObject is not supposed to be used outside of its managedObjectContexts thread/queue (sometime it works and some times you crash ==> don't do it).
CoreData does not guarantee safe read access to the object.
To access an object owned by a "private queue" context, always use either [context performBlock:...] or [context performBlockAndWait:...], unless you access its objectID or managedObjectContext properties.
You do need to use performBlock: or performBlockAndWait:, but there's one exception. If you're using NSMainQueueConcurrencyType and you are using the managed object on the main queue, you can access it directly, with no block. This can be a great convenience when you need to update your UI from a managed object, or vice versa.

Associating a NSThread with a NSManagedObject

I am creating a NSManagedObject (subclass) with certain attributes. At the same time, I am executing some code/a block that does some network operation given the attributes of my NSManagedObject. Now, some times that network operation might fail or take too long, so I want to add the ability to cancel the execution of that code/block.
I was thinking of making the code/block an NSThread, and then I have the ability to call [theThread cancel]. However, how do I associate the NSThread with my NSManagedObject, given that I cannot add properties to NSManagedObject Categories? Is it OK to just add the property to the definition of the NSManagedObject itself? Seems legal, but subsequent changes to the Core Data model would overwrite my code, I guess.
But maybe there is an entirely different and better way to accomplish what I am trying to do? Any ideas?
First, new code really should prefer GCD or NSOperationQueue over NSThread. If you find yourself using NSThread it's time to slow down and revisit your design and implementation requirements.
Second, using NSManagedObject across threads is really, really bad. If you do anything but exceedingly trivial things, it can get very difficult to do right as well.
Finally, no matter how you do your threaded network access, you should prefer to grab the data from the managed object, and pass that instead of the managed object itself
If you must access the managed object, make sure your managed object context is of either NSMainQueueConcurrencyType or NSPrivateQueueConcurrencyType and access the managed object like by invoking performBlock or performBlockAndWait using the managedObjectContext property of the managed object.
EDIT
Ok, let me check this with you. What I a currently doing is spawning a
backgroundContext, create a new NSManagedObject using performBlock,
then save that background Context, switch to the parent context (using
performblock), obtain the newly created object in that context using
existingObjectWithId:. Then, I create a NSOperation subclass, tie the
NSManagedObject (from the parent context) to that NSOperation subclass
(it's a property on the subclass) and put that operation in a
NSOperationQueue. Within that NSOperation, the NSManagedObject gets
changed. It seems to work fine, does that look ok? – user1013725
Um... maybe??? I didn't follow that. Could you please post the code? That would be much more precise and more easy to understand.
#JodyHagins So I am not using performBlock, but maybe that's ok
because the managedObjectContext is the main context? – user1013725
No.
If the main context is created with either alloc] init] or alloc] initWithConcurrencyType:NSConfinementConcurrencyType then you must use it only when you know you are running on the main thread.
If it is created with alloc] initWithConcurrencyType:NSMainThreadConcurrencyType then you must use it only when you know you are running on the main thread or within one of the performBlock methods.

What is NSManagedObjectContext's performBlock: used for?

In iOS 5, NSManagedObjectContext has a couple of new methods, performBlock: and performBlockAndWait:. What are these methods actually used for? What do they replace in older versions? What kind of blocks are supposed to be passed to them? How do I decide which to use? If anyone has some examples of their use it would be great.
The methods performBlock: and performBlockAndWait: are used to send messages to your NSManagedObjectContext instance if the MOC was initialized using NSPrivateQueueConcurrencyType or NSMainQueueConcurrencyType. If you do anything with one of these context types, such as setting the persistent store or saving changes, you do it in a block.
performBlock: will add the block to the backing queue and schedule it to run on its own thread. The block will return immediately. You might use this for long persist operations to the backing store.
performBlockAndWait: will also add the block to the backing queue and schedule it to run on its own thread. However, the block will not return until the block is finished executing. If you can't move on until you know whether the operation was successful, then this is your choice.
For example:
__block NSError *error = nil;
[context performBlockAndWait:^{
myManagedData.field = #"Hello";
[context save:&error];
}];
if (error) {
// handle the error.
}
Note that because I did a performBlockAndWait:, I can access the error outside the block. performBlock: would require a different approach.
From the iOS 5 core data release notes:
NSManagedObjectContext now provides structured support for concurrent operations. When you create a managed object context using initWithConcurrencyType:, you have three options for its thread (queue) association
Confinement (NSConfinementConcurrencyType).
This is the default. You promise that context will not be used by any thread other than the one on which you created it. (This is exactly the same threading requirement that you've used in previous releases.)
Private queue (NSPrivateQueueConcurrencyType).
The context creates and manages a private queue. Instead of you creating and managing a thread or queue with which a context is associated, here the context owns the queue and manages all the details for you (provided that you use the block-based methods as described below).
Main queue (NSMainQueueConcurrencyType).
The context is associated with the main queue, and as such is tied into the application’s event loop, but it is otherwise similar to a private queue-based context. You use this queue type for contexts linked to controllers and UI objects that are required to be used only on the main thread.
They allow you to access the same managedObjectContext accross threads.
I am not really sure I am correct, but this is how I use it.
You use performBlockAndWait is like "usual". You do not need it if you execute the managedObjectContext only on one thread. If you execute it on many threads then yes you will need performBlock.
So, if you're on main thread, you do not need to do performBlockAndWait for the main managedObjectContext. At least I don't and is doing fine.
However if you access that managedObjectContext on other threads then yes you will need to do performBlockAndWait.
So that's the purpose of performBlock and performBlockAndWait.
Would someone please correct me if I am wrong here. Of course if you access the context only on one thread then you can simply use the default.

Resources