I'm creating an NSManagedObjectContext
in a private queue to handle data updates I take from files and/or services:
NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
privateContext.persistentStoreCoordinator = appDelegate.persistentStoreCoordinator;
Since I'm using a private queue, I don't fully understand the difference between performBlock:
and performBlockAndWait:
methods... To perform my data updates I'm currently doing this:
[privateContext performBlock: ^{
// Parse files and/or call services and parse
// their responses
// Save context
[privateContext save:nil];
dispatch_async(dispatch_get_main_queue(), ^{
// Notify update to user
});
}];
In this case, my data updates are made synchronoulsy and sequentially, so I suppose that is the correct place to save the context, right? If I'm doing something wrong, I'd appreciate if you let me know. On the other hand, would this code be equivalent?:
[privateContext performBlockAndWait: ^{
// Parse files and/or call services and parse
// their responses
// Save context
[privateContext save:nil];
}];
// Notify update to user
Again I guess that is the correct place to save the context... what are the differences between both methods (if any, in this case)?
What if instead of performing synchronous service calls or files parsing, I need to perform asynchronous service calls? How would these data updates be managed?
Thanks in advance
You are correct in that anything you want to do with a MOC must be done within either performBlock
or performBlockAndWait
. Note that retain/release is thread safe for managed objects, so you don't have to be inside one of those blocks to retain/release reference counts on managed objects.
They both utilize a synchronous queue to process messages, which means that only one block will execute at a time. Well, that's almost true. See the descriptions of performBlockAndWait
. In any event, the access to the MOC will be serialized such that only one thread is accessing the MOC at a time.
tl;dr Don't worry about the difference, and always use performBlock
.
There are a number of differences. I'm sure there are more, but here are the ones that I think are most important to understand.
performBlock
is asynchronous, in that it returns immediately, and the block is executed at some time in the future, on some undisclosed thread. All blocks given to the MOC via performBlock
will execute in the order they were added.
performBlockAndWait
is synchronous, in that the calling thread will wait until the block has executed before returning. Whether the block runs in some other thread, or runs in the calling thread is not all that important, and is an implementation detail that can't be trusted.
Note, however, that it could be implemented as "Hey, some other thread, go run this block. I'm gonna sit here doing nothing until you tell me it's done." Or, it could be implemented as "Hey, Core Data, give me a lock that prevents all those other blocks from running so I can run this block on my own thread." Or it could be implemented in some other way. Again, implementation detail, which could change at any time.
I'll tell you this though, the last time I tested it, performBlockAndWait
executed the block on the calling thread (meaning the second option in the above paragraph). This is only really information to help you understand what is going on, and should not be relied upon in any way.
performBlock
is always asynchronous, and is thus not reentrant. Well, some may consider it reentrant, in that you can call it from within a block that was called with performBlock
. However, if you do this, all calls to performBlock
will return immediately, and the block will not execute until at least the currently executing block completely finishes its work.
[moc performBlock:^{
doSomething();
[moc performBlock:^{
doSomethingElse();
}];
doSomeMore();
}];
These functions will always be executed in this order:
doSomething()
doSomeMore()
doSomethingElse()
performBlockAndWait
is always synchronous. Furthermore, it is also reentrant. Multiple calls will not deadlock. Thus, if you end up calling performBlockAndWait
while you are inside a block that was being run as a result of another performBlockAndWait
, then it's OK. You will get the expected behavior, in that the second call (and any subsequent calls) will not cause a deadlock. Furthermore, the second one will completely execute before it returns, as you would expect.
[moc performBlockAndWait:^{
doSomething();
[moc performBlockAndWait:^{
doSomethingElse();
}];
doSomeMore();
}];
These functions will always be executed in this order:
doSomething()
doSomethingElse()
doSomeMore()
FIFO stands for "First In First Out" which means that blocks will be executed in the order in which they were put into the internal queue.
performBlock
always honors the FIFO structure of the internal queue. Every block will be inserted into the queue, and only run when it is removed, in FIFO order.
By definition, performBlockAndWait
breaks FIFO ordering because it jumps the queue of blocks that have already been enqueued.
Blocks submitted with performBlockAndWait
do not have to wait for other blocks that are running in the queue. There are a number of ways to see this. One simple one is this.
[moc performBlock:^{
doSomething();
[moc performBlock:^{
doSomethingElse();
}];
doSomeMore();
[moc performBlockAndWait:^{
doSomethingAfterDoSomethingElse();
}];
doTheLastThing();
}];
These functions will always be executed in this order:
doSomething()
doSomeMore()
doSomethingAfterDoSomethingElse()
doTheLastThing()
doSomethingElse()
It's obvious in this example, which is why I used it. Consider, however, if your MOC is getting stuff called on it from multiple places. Could be a bit confusing.
The point to remember though, is that performBlockAndWait
is preemptive and can jump the FIFO queue.
You will never get a deadlock calling performBlock
. If you do something stupid inside the block, then you could deadlock, but calling performBlock
will never deadlock. You can call it from anywhere, and it will simply add the block to the queue, and execute it some time in the future.
You can easily get deadlocks calling performBlockAndWait
, especially if you call it from a method that an external entity can call or indiscriminately within nested contexts. Specifically, you are almost guaranteed to deadlock your applications if a parent calls performBlockAndWait
on a child.
Core Data considers a "user event" to be anything between calls to processPendingChanges
. You can read the details of why this method is important, but what happens in a "user event" has implications on notifications, undo management, delete propagation, change coalescing, etc.
performBlock
encapsulates a "user event" which means the block of code is automatically executed between distinct calls to processPendingChanges
.
performBlockAndWait
does not encapsulate a "user event." If you want the block to be treated as a distinct user event, you must do it yourself.
performBlock
wraps the block in its own autoreleasepool.
performBlockAdWait
does not provide a unique autoreleasepool. If you need one, you must provide it yourself.
Personally, I do not believe there are very many good reasons to use performBlockAndWait
. I'm sure someone has a use case that can't be accomplished in any other way, but I've yet to see it. If anyone knows of that use case, please share it with me.
The closest is calling performBlockAndWait
on a parent context (don't ever do this on a NSMainConcurrencyType
MOC because it could lock up your UI). For example, if you want to ensure that the database has completely saved to disk before the current block returns and other blocks get a chance to run.
Thus, a while ago, I decided to treat Core Data as a completely asynchronous API. As a result, I have a whole lot of core data code, and I do not have one single call to performBlockAndWait
, outside of tests.
Life is much better this way. I have much fewer problems than I did back when I thought "it must be useful or they wouldn't have provided it."
Now, I simply no longer have any need for performBlockAndWait
. As a result, maybe it has changed some, and I just missed it because it no longer interests me... but I doubt that.