这个话题已经在许多论坛上讨论过了,但是我仍然不能完全理解 performBlockAndWait
是如何工作的。根据我的理解,context.performBlockAndWait(block: () -> Void)
会在它自己的队列中执行该块并阻塞调用线程。文档说:
您可以在一个块内部将“标准”消息分组发送到上下文中以传递给这些方法。
什么是“标准”消息? 它还说:
基于队列的托管对象上下文上的Setter方法是线程安全的。您可以直接在任何线程上调用这些方法。
这是否意味着我可以在 performBlock
* APIs 的上下文之外设置已获取的托管对象的属性?
据我所知,在具有并发类型 .MainQueueConcurrencyType
的上下文上调用 performBlockAndWait(block: () -> Void)
将在从主线程调用时创建死锁并永久阻塞 UI。但是在我的测试中,它并没有创建任何死锁。
我认为它应该会创建死锁的原因是,performBlockAndWait 首先会阻塞调用线程,然后在它自己的线程上执行块。由于上下文必须执行其块的线程与已经被阻塞的调用线程相同,因此它将永远无法执行其块并且线程将一直保持阻塞状态。
但是我在一些奇怪的场景中遇到了死锁。我有以下测试代码:
@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)
}
}
请注意,在我的测试代码的fetchDepartment
方法中,我意外地两次粘贴了performBlockAndWait
。
- 如果没有调用
fetchAllStudentsOfDepartment
方法,则不会导致死锁。 但是一旦调用fetchAllStudentsOfDepartment
,则对fetchDepartment
方法的任何调用都会永久阻塞UI。 - 如果我删除
fetchAllStudentsOfDepartment
方法中的print(student.firstName)
,那么它就不会阻塞。这意味着,仅当访问关系的属性时,它才会阻塞UI。 privateContext
的concurrencyType
设置为.PrivateQueueConcurrencyType
。上面的代码仅在privateContext
的parentContext
的concurrencyType
设置为.MainQueueConcurrencyType
时才会阻塞UI。我已经使用其他
.xcdatamodel
测试了相同的代码,并且现在我确定只有在访问关系的属性时才会阻塞。 我当前的.xcdatamodel
如下所示:
如果信息有冗余,请谅解,但是我只是分享了在已经花费了大约8个小时后的所有观察结果。当UI被阻止时,我可以发布我的线程堆栈。 总之,我有三个问题:
- 什么是“标准”消息?
- 我们可以设置在
performBlock
* API之外的上下文中获取的托管对象的属性吗? - 为什么在我的测试代码中,
performBlockAndWait
的表现不佳并导致UI阻塞。
测试代码:您可以从此处下载测试代码。
performBlockAndWait
几乎总是一个坏主意。 - Marcus S. Zarra