So I've been reading about and working with Core Data pretty extensively over the past few weeks, and I've ended up implementing my Core Data stack using the following setup:
Save context - root context tied to the store, on PrivateQueue
Main context - running on main thread, child of save context
Edit contexts - created on-demand in any thread to edit UI data in the background as children of main context
With this setup, I haven't been having any issues, and I've taken the necessary precautions to prevent multiple background edit contexts from calling [editMoc save] simultaneously, but I'm not seeing how this is safe relative to the main thread/context. Specifically, all the code I keep seeing while using this setup is like the following:
[edit performBlock^{
[edit save:nil];
[main performBlock:^{
[main save:nil];
[root performBlock...]
}];
}];
Now the thing I have not been able to figure out is this: every time the child context saves, the data is being propagated to the main context, but nothing is happening to ensure that the data isn't currently being read from/acted upon in the main context, right? Essentially, I feel like something like this would be more appropriate:
[edit performBlock^{
dispatch_async(dispatch_get_main_queue(), ^{
[edit save:nil];
});
[main performBlock:^{
[main save:nil];
[root performBlock....]
}];
}];
At least if it were done that way, we'd ensure that the propagation is safe relative to the main thread/UI, no? This sort of ties in with my other question of "why should the main context really be associated with the main thread anyways?" Right now if I need to execute a fetch, I do it from the main context and I can understand that it then makes sense for it to be on the main thread, but if propagations can just happen "whenever" (since we aren't doing them on the main thread) what is the ultimate point of having it tied to the main thread? I don't even need to do a fetch because the data has seemingly unsafely arrived in the main context from the child save, yeah? From there a simple [tableView reload] suffices.
Any help is appreciated!
EDIT:
So I think I've found the answer to my question, which is that Core Data does in fact internally push the changes from the child context to the parent context using the correct thread (i.e. using the parent context's queue - which in this case would be the main queue). Meaning that it would in fact have to wait for anything else happening on the main thread that may have been using that data before it will dirty the main context.
Related
Problem Statement
I am having trouble saving server side data asynchronously.
Structure
I am using the following structure of NSManagedObjectContext in order of parent to child:
writerManagedObjectContext (NSPrivateQueueConcurrencyType)
masterManagedObjectContext (NSMainQueueConcurrencyType)
backgroundManagedObjectContext (NSPrivateQueueConcurrencyType)
Code
I am using the following code to save data
[backgroundManagedObjectContext performBlock:^{
[backgroundManagedObjectContext save:nil];
[masterManagedObjectContext performBlock:^{ // Starts blocking UI from here
[masterManagedObjectContext save:nil];
[writerManagedObjectContext performBlock:^{
[writerManagedObjectContext save:nil];
}]
}]
}]
Problem
The code saves fine. The backgroundManagedObjectContext also saves asynchronously. However, both masterManagedObjectContext and writerManagedObjectContext refuse to be saved asynchronously and blocks the UI thread. (I know it blocks the UI thread because I tried to perform actions which have nothing to do with Core Data and they were also blocked. This is not a case of the persistent coordinator not being accessible)
Questions
What would be the reason the above code blocks the main thread?
Am I correct in assuming I can call the above code from anywhere since the save will be called in each respective thread/context?
Any help would be much appreciated.
Edit
http://floriankugler.com/2013/04/29/concurrent-core-data-stack-performance-shootout/
Apparently the freeze comes from the attempt to propagate to the parent NSManagedObjectContext. The article seems to elude to the fact that it is impossible to have a truly asynchronous save to the main context.
The data was heavily linked, 5MB, and took approximately 40s to save to psc. I don't think I'll use a parallel structure as described in the post since the code base is already large as it is. I would appreciate any strategies I can utilize to decrease this freeze.
Even though backgroundManagedObjectContext is a private queue context it still propagates its changes up to masterManagedObjectContext, being its parent. This could be where it's choking up. Merging changes from child contexts still takes up CPU time, the effects of which become more pronounced in a busy queue like the UI's.
You can always use Instruments to analyze what's going on.
If your particular use case permits, try setting backgroundManagedObjectContext.persistentStoreCoordinator to the same psc as writerManagedObjectContext and not make it a child of masterManagedObjectContext.
Better yet, use an awesome framework like MagicalRecord. Doesn't shield you from problems like this, but less code makes things (arguably) a bit easier to debug.
I have an application with Core Data where I frequently use:
[[MyCoreDataStore defaultStore].mainQueueContext performBlockAndWait:^{
// perform some operations
[MyCoreDataStore saveMainQueueContext];
}];
During an external code audit it was pointed out that performBlockAndWait: on the main context was wrong.
I was reading some questions from StackOverflow but could not find the canonical way to handle concurrency and not block the main context
Core Data main context concurrency
NSManagedObjectContext performBlockAndWait: doesn't execute on background thread?
performBlockAndWait On Child Context with Private Queue Deadlocks Parent on iOS 7
Multiple contexts in the main thread: why and when use them?
So what's the canonical way to deal with concurrence and keep the main context unblocked?
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.
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.