访问关系

时间:2016-01-02 07:29:10

标签: ios multithreading swift core-data nsmanagedobjectcontext

许多论坛都讨论了这个主题,但我仍然无法完全理解performBlockAndWait实际上是如何运作的。根据我的理解,context.performBlockAndWait(block: () -> Void)将在阻塞调用程序线程的同时在其自己的队列中执行该块。 Documentation说:

  

将“标准”消息分组以发送到块中的上下文   传递给其中一种方法。

什么是"标准"消息?它还说:

  

基于队列的托管对象上下文的Setter方法是线程安全的。   您可以直接在任何线程上调用这些方法。

这是否意味着我可以设置托管对象的属性,该托管对象是在performBlock * API之外的上下文的performBlock * API内提取的?

根据我的理解,在并发类型为performBlockAndWait(block: () -> Void)的上下文中调用.MainQueueConcurrencyType将在从主线程调用时永远创建死锁并阻止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)
    }
}

请注意,我在测试代码中偶然在performBlockAndWait方法中粘贴fetchDepartment两次。

  • 如果我没有打电话,它不会造成任何死锁 fetchAllStudentsOfDepartment方法。但是一旦我打电话 fetchAllStudentsOfDepartment,对fetchDepartment方法的任何调用 永远阻止用户界面。
  • 如果我在print(student.firstName)方法中移除了fetchAllStudentsOfDepartment,则它不会阻止。这意味着,只有在我访问关系的属性时才会阻止UI。
  • privateContextconcurrencyType设置为.PrivateQueueConcurrencyType。仅当privateContext parentContext concurrencyType设置为.MainQueueConcurrencyType时,上述代码才会阻止用户界面。

    我已经使用其他.xcdatamodel测试了相同的代码,现在我确信它只会阻止访问关系的属性。我目前的.xcdatamodel看起来像: data model

请原谅我,如果这些信息是无关紧要的,但我只是花了8个小时后才分享我的所有观察结果。当UI被阻止时,我可以发布我的线程堆栈。总而言之,我有三个问题:

  1. 什么是"标准"消息?
  2. 我们可以设置托管对象的属性,该托管对象在performBlock * {* 1}}以外的上下文的performBlock * API内提取吗?
  3. 为什么performBlockAndWait行为不端并导致测试代码中的UI阻塞。
  4. 测试代码:您可以从here下载测试代码。

2 个答案:

答案 0 :(得分:8)

  1. 标准消息是旧的Objective-C术语。这意味着您应该对performBlockperformBlockAndWait中的ManagedObjectContext及其子ManagedObjects执行所有常规方法调用。在块之外的私有上下文中允许的唯一调用是initsetParentContext。其他任何事都应该在一个区块内完成。

  2. 没有。从私有上下文获取的任何托管对象只能在该私有上下文的队列中访问。从另一个队列访问(读取或写入)违反了线程限制规则。

  3. 您遇到阻塞问题的原因是因为您有两个级别的“mainQueue”上下文,并且“超出”队列系统。这是流程:

    • 您在主队列上创建一个上下文,然后将其创建为另一个主队列上下文的子项。
    • 您创建第二层主队列上下文的私有子项
    • 您访问该私有队列上下文时,它正在尝试在当前已加载到主队列上下文中的对象中发生故障。
  4. 由于主队列上下文的两个级别,它导致死锁,通常队列系统会看到潜在的死锁并避免死锁。

    您可以将mainContext变量更改为:

    进行测试
    lazy var mainContext: NSManagedObjectContext = {
        let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate
        return appDelegate!.managedObjectContext
    }
    

    你的问题就消失了,因为队列系统会看到阻止并避开它。您甚至可以通过在performBlockAndWait()内放置一个断点来查看发生的情况,并看到您仍在主队列中。

    最后,没有理由在父/子设计中有两级主队列上下文。如果有的话,这不是一个很好的论据。

    更新

    我错过了您更改了appDelegate中的模板代码,并将整体上下文转换为私有内容。

    每个vc拥有一个主要MOC的模式会丢掉Core Data的许多好处。虽然在顶部有一个私有和一个主MOC(整个应用程序存在,而不仅仅是一个VC)是一个有效的设计,如果你从主队列中执行这样的performBlockAndWait,它将无法工作。 / p>

    我不建议您在阻止整个应用程序时使用主队列中的performBlockAndWait。只有在调用 TO 主队列(或者可能是一个背景到另一个背景)时才应使用performBlockAndWait

答案 1 :(得分:4)

  
      
  1. 什么是"标准"消息?
  2.   

发送到托管对象上下文或任何托管对象的任何消息。请注意,文档继续澄清......

There are two exceptions:

* Setter methods on queue-based managed object contexts are thread-safe.
  You can invoke these methods directly on any thread.

* If your code is executing on the main thread, you can invoke methods on the
  main queue style contexts directly instead of using the block based API.

因此,必须从performBlock内部调用除MOC上的setter方法之外的任何内容。可以从主线程调用MOC上NSMainQueueConcurrencyType的任何方法而不包含在performBlock内。

  
      
  1. 我们可以设置在performBlock *之外的上下文的performBlock * API中获取的托管对象的属性吗?
  2.   

没有。必须保护托管对象的任何访问权限,以免受托管对象所在的托管对象上下文中的performBlock内部的访问。请注意驻留在从主队列访问的主队列MOC中的托管对象的异常。

  
      
  1. 为什么performBlockAndWait在我的测试代码中行为不端并导致UI阻塞。
  2.   

这不是行为不端。 performBlockAndWait是可重入的,但仅限于已处理performBlock[AndWait]来电时。

除非您没有其他选择,否则不应使用performBlockAndWait。嵌套上下文尤其有问题。

改为使用performBlock