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.
Related
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.
What I'm trying to do:
perform background sync with a web API without freezing the UI. I'm using MagicalRecord but it's not really specific to it.
make sure I'm using contexts & such correctly
What my question really is: is my understanding correct? Plus a couple questions at the end.
So, the contexts made available by MagicalRecord are:
MR_rootSavingContext of PrivateQueueConcurrencyType which is used to persist data to the store, which is a slow process
MR_defaultContext of MainQueueConcurrencyType
and for background you would want to work with a context generated by MR_context(), which is a child of MR_defaultContext and is of PrivateQueueConcurrencyType
Now, for saving in an asynchronous way, we have two options:
MR_saveToPersistentStoreWithCompletion() which will save all the way up to MR_rootSavingContext and write to disk
MR_saveOnlySelfWithCompletion() which will save only up to the parent context (i?e. MR_defaultContext for a context created with MR_context)
From there, I thought that I could do the following (let's call it Attempt#1) without freezing the UI:
let context = NSManagedObjectContext.MR_context()
for i in 1...1_000 {
let user = User.MR_createInContext(context) as User
context.MR_saveOnlySelfWithCompletion(nil)
}
// I would normally call MR_saveOnlySelfWithCompletion here, but calling it inside the loop makes any UI block easier to spot
But, my assumption was wrong. I looked into MR_saveOnlySelfWithCompletion and saw that it relies on
[self performBlock:saveBlock];
which according to Apple Docs
Asynchronously performs a given block on the receiver’s queue.
So I was a bit puzzled, since I would expect it not to block the UI because of that.
Then I tried (let's call it Attempt#2)
let context = NSManagedObjectContext.MR_context()
for i in 1...1_000 {
let user = User.MR_createInContext(context) as User
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in
context.MR_saveOnlySelfWithCompletion(nil)
}
}
And this does the job, but it doesn't feel right.
Then I found something in the release notes of iOS 5.0
When sending messages to a context created with a queue
association, you must use the performBlock: or performBlockAndWait:
method if your code is not already executing on that queue (for the
main queue type) or within the scope of a performBlock... invocation
(for the private queue type). Within the blocks passed to those
methods, you can use the methods of NSManagedObjectContext freely.
So, I'm assuming that:
Attempt#1 freezes the UI because I'm actually calling it from the main queue and not within the scope of a performBlock
Attempt#2 works, but I'm creating yet another thread while the context already has its own background thread
So of course what I should do is use saveWithBlock:
MagicalRecord.saveWithBlock { (localContext) -> Void in
for i in 1...1_000 {
User.MR_createInContext(context)
}
}
This performs the operation on a direct child of MR_rootSavingContext which is of PrivateQueueConcurrencyType.
Thanks to rootContextChanged, any change that goes up to MR_rootSavingContext will be available to MR_defaultContext.
So it seems that:
MR_defaultContext is the perfect context when it comes to displaying data
edits are preferably done in an MR_context (child of MR_defaultContext)
long running tasks such as a server sync are preferably done using saveWithBlock
What it still don't get is how to work with MR_save[…]WithCompletion(). I would use it on MR_context but since it blocked the main thread in my test cases I don't see when it becomes relevant (or what I missed…).
Thanks for your time :)
Ok, I am rarely using magical records but since you said you question is more general I will attempt an answer.
Some theory: When creating a context you pass an indicator as to whether you want it to be bound on the main or a background thread
let context = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
By "bound" we mean that a thread is referenced by the context internally. In the example above a new thread is created and owned by the context. This thread is not used automatically but must be called explicitly as:
context.performBlock({ () -> Void in
context.save(nil)
return
});
So your code with 'dispatch_async' is wrong because the thread the context is bound to can only be referenced from the context itself (it is a private thread).
What you have to infer from the above is that if the context is bound to the main thread, calling performBlock from the main thread will not do anything different that calling context methods straight.
To comment on your bullet points at the end:
MR_defaultContext is the perfect context when it comes to displaying data: An NSManagedObject must be accessed from the context it is
created so it is actually the only context that you can feed the
UI from.
edits are preferably done in an MR_context (child of MR_defaultContext): Edits are not expensive and you should follow
the rule above. If you are calling a function that edits an NSManagedObject's properties from the main thread (like at the tap of a button)
you should update the main context. Saves on the other hand are
expensive and this is why your main context should not be linked to a
persistent store directly but just push its edits down to a root
context with background concurrency owning a persistent store.
long running tasks such as a server sync are preferably done using saveWithBlock Yes.
Now, In attempt 1
for i in 1...1_000 {
let user = User.MR_createInContext(context) as User
}
context.MR_saveOnlySelfWithCompletion(nil)
There is no need to save for every object creation. Even if the UI was not blocked it is wasteful.
About MR_context. In the documentation for magical records I cannot see a 'MR_context' so I am wondering if it is a quick method to access the main context. If it is so, it will block.
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.
So, after learning about completion blocks a while back, I like using completion blocks a lot. I like closure and I like the ability to pass just about anything anywhere I want.
As someone who's new to thread programming, I've been staying away from GCD and NSOperation--but lately I've had to program asynchronous updated to Core Data stuff and I am beginning to have doubts to my "all completion block all the time" approach.
So here's one example of what I'm questioning myself: I have a series of potentially rather large data (images, sound, video, what have you) to upload to a server somewhere. The metadata for these data is stored in Core Data, and I have a timestamp that I use to decide which objects should get uploaded. All these uploads should be done sequentially.
What I have coded is a essentially basically just a function with a completion block in it, that has a call to itself at the end of the block, like this:
(void)uploadAllAsynchronously {
... // First figure out what to upload based on core data
// Here comes the completion block in question
void(^blk)(BOOL) = ^(BOOL)uploadSuccess {
... // if upload successful, update core data to mark what has been uploaded
[self uploadAllAsynchronously]; // Recursively calls the function that contains this block. I actually have a weak self, or if that fails to break a retain cycle, I should be able to pass in a NSManagedObjectContext as an argument.
}
[NSURLConnection sendAsynchronousRequest:... queue:... completionHandler:blk];
}
This should work, right? Is there something here that is completely dangerous that suggests I have to use GCD and handling my own queue? I am asking this because I'm kind of having some trouble right now possibly with data in it would appear different threads not updating correctly because of asynchronous calls, though not sure which part of my code is the culprit.
Thanks in advance.
Your block is of the wrong type.
As the documentation for
+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse *, NSData *, NSError *))handler
shows, the type of the completion black is
void (^) (NSURLResponse *, NSData *, NSError *)
not
void (^) (BOOL)
You should change blk to be something like
void (^blk) (NSURLResponse *, NSData *, NSError *) = ^ (NSURLResponse *response, NSData *data, NSError *error) {
//...
};
It would be more stylish to write
[NSURLConnection sendAsynchronousRequest:theRequest queue:theQueue completionHandler:^ (NSURLResponse *response, NSData *data, NSError *error) {
//...
}];
with the completion block in-line with the method.
Regarding the question of performing operations on your NSManagedObjectContext in the completion handler: This is fine so long as the NSOperationQueue passed to sendAsynchronousRequest:queue:completionHandler: is the same as the one in which the managed object context is created. But as the documentation for NSManagedObjectContext states
Core Data uses thread (or serialized queue) confinement to protect managed objects and managed object contexts (see “Concurrency with Core Data”). A consequence of this is that a context assumes the default owner is the thread or queue that allocated it—this is determined by the thread that calls its init method. You should not, therefore, initialize a context on one thread then pass it to a different thread. Instead, you should pass a reference to a persistent store coordinator and have the receiving thread/queue create a new context derived from that.
If the queue that you pass is not the one on which you created the managed object context, you must do one of the following
Call -[NSOperationQueue addOperation:] on the queue where the managed object context was created.
Create a second managed object context (with the same persistent store coordinator) on the queue on which the core data operations are taking place.
Create a second managed object context and a second persistent store coordinator on the queue on which the core data operations are taking place.
Use locking.
The documentation on Concurrency with Core Data makes it clear that you must either use thread confinement (options 1-3 above) or use locking (option 4 above).
This is what the docs have to say about using locks:
If you choose not to use the thread containment pattern—that is, if you try to pass managed objects or contexts between threads, and so on—you must be extremely careful about locking, and as a consequence you are likely to negate any benefit you may otherwise derive from multi-threading.
This is what the docs have to say about having not just per-thread managed object contexts but also per-thread persistent store coordinators:
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.
yes this code should work.. note: I'd rename the method then.. uploadIfNeeded maybe -- because it doesnt always blindly upload stuff...
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.