This topic has been discussed at many forum, but I am still not able to fully understand how performBlockAndWait
actually works. As per my understanding, context.performBlockAndWait(block: () -> Void)
will perform the block in its own queue while blocking the caller thread. Documentation says that:
You group “standard” messages to send to the context within a block to pass to one of these methods.
What are the "standard" messages? It also says that:
Setter methods on queue-based managed object contexts are thread-safe. You can invoke these methods directly on any thread.
Does that mean that I can set properties of a managed object which is fetched inside performBlock
* API of a context outside performBlock
* APIs?
As per my understanding, calling performBlockAndWait(block: () -> Void)
on context with concurrency type .MainQueueConcurrencyType
will create a deadlock and block UI forever when called from main thread. But in my tests, its not creating any deadlock.
The reason why I think that it should create a deadlock is that, performBlockAndWait will first block the caller thread, and then execute the block on its own thread. Since the thread in which context has to execute its block is the same as the caller thread which is already blocked, so it will never be able to execute its block and the thread remains blocked forever.
However I am facing deadlocks in some weird scenario. I have below test code:
@IBAction func fetchAllStudentsOfDepartment(sender: AnyObject) {
let entity = NSEntityDescription.entityForName("Department", inManagedObjectContext: privateContext)
let request = NSFetchRequest()
request.entity = entity
request.relationshipKeyPathsForPrefetching = ["students"]
var department: Department?
privateContext.performBlockAndWait { () -> Void in
department = try! self.privateContext.executeFetchRequest(request).first as? Department
print(department?.name)
guard let students = department?.students?.allObjects as? [Student] else {
return
}
for student in students {
print(student.firstName)
}
}
}
@IBAction func fetchDepartment(sender: AnyObject) {
let entity = NSEntityDescription.entityForName("Department", inManagedObjectContext: privateContext)
let request = NSFetchRequest()
request.entity = entity
privateContext.performBlockAndWait { () -> Void in
let department = try! self.privateContext.executeFetchRequest(request).first as? Department
print(department?.name)
}
privateContext.performBlockAndWait { () -> Void in
let department = try! self.privateContext.executeFetchRequest(request).first as? Department
print(department?.name)
}
}
Note that I accidentally pasted performBlockAndWait
twice in fetchDepartment
method in my test code.
- It does not create any deadlock if I have not called
fetchAllStudentsOfDepartment
method. But once I callfetchAllStudentsOfDepartment
, any call tofetchDepartment
method blocks the UI forever. - If I remove
print(student.firstName)
infetchAllStudentsOfDepartment
method, then it does not block. That means, it blocks UI only if I access a relationship's property. privateContext
hasconcurrencyType
set to.PrivateQueueConcurrencyType
. The above code blocks UI only whenprivateContext
'sparentContext
hasconcurrencyType
set to.MainQueueConcurrencyType
.I have tested the same code with other
.xcdatamodel
as well and I am sure now that it only blocks if a relationship's property is accessed. My current.xcdatamodel
looks like:
Pardon me if the information is extraneous, but I am just sharing all my observations after spending like 8 hours already. I can post my thread stack when UI is blocked. To summarize, I have three questions:
- What are the "standard" messages?
- Can we set properties of a managed object which is fetched inside
performBlock
* API of a context outsideperformBlock
*? - Why
performBlockAndWait
is misbehaving and causing UI block in my test code.
TEST CODE: You can download the test code from here.
performBlockAndWait
from the main queue is almost always a bad idea. – Bassoon