【问题标题】:How to save to managed object context in a background thread in Core Data如何在 Core Data 的后台线程中保存到托管对象上下文
【发布时间】:2018-09-02 10:38:49
【问题描述】:

我有一个应用程序需要大约一分钟的时间来设置,所以当用户点击“开始新游戏”时,我想在数据加载到 Core Data 时显示一个活动微调器。

我知道我必须在后台线程上执行此操作,以便我可以在主线程上更新 UI,但我不知道如何在后台线程中保存托管上下文。这是我目前所拥有的:

func startNewGame() {
  initiateProgressIndicator() // start the spinner and 'please wait' message

  DispatchQueue.global(qos: .background).async {
    self.coreDataStack.importDefaultData() // set up the database for the new game

    DispatchQueue.main.async {
        stopProgressIndicator()

        // Transition to the next screen
        let vc: IntroViewController = self.storyboard?.instantiateViewController(withIdentifier: "IntroScreen") as! IntroViewController
        vc.rootVCReference = self
        vc.coreDataStack = self.coreDataStack
        self.present(vc, animated:true, completion:nil)
    }
}

在 importDefaultData() 中我需要保存多次,但是当我尝试这样做时它会崩溃。我现在明白这是因为我试图从后台线程访问主上下文。下面是函数的基本结构:

func importDefaultData() {
    // import data into Core Data here, code not shown for brevity

    saveContext()

    // import more data into Core Data here, code not shown for brevity

    saveContext()

    // import final data here

    saveContext()
}

根据我的阅读,我认为我需要为后台线程创建另一个托管对象上下文,但我不清楚如何去做以及如何将其融入我当前的代码中。在进入下一个屏幕之前,我也不知道如何确保正确保存数据。我没有多线程方面的经验,因此我们将不胜感激。

编辑:关于被标记为重复,建议的主题是 8 岁,不包含任何答案。我希望通过 Swift 中的简短代码示例获得更新的答案。

【问题讨论】:

  • 链接帖子中的 Apple Doc 链接似乎不再有效
  • let childContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) childContext.parent = parentContext
  • context.performAndWait { }context.perform { }内进行所有更改和保存
  • 感谢@user1046037,您的 cmets 让我走上了正确的道路 :)

标签: ios swift multithreading core-data


【解决方案1】:

在阅读了来自 @user1046037 的有用 cmets(谢谢!)并研究了如何使用 context.perform { }context.performAndWait { } 之后,我已经解决了这个问题。我将在下面发布我的代码以防其他人受益,因为我在 swift 中找不到任何关于 SO 的示例:

initiateProgressIndicator() // start the spinner and 'please wait' message

let childContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
childContext.parent = coreDataStack.managedContext

// Create a background task
childContext.perform {
  // Perform tasks in a background queue
  self.coreDataStack.importDefaultData(childContext: childContext) // set up the database for the new game

  do {
    // Saves the tasks done in the background to the child context
    try childContext.save()

    // Performs a task in the main queue and wait until this tasks finishes
    self.coreDataStack.managedContext.performAndWait {
        do {
            // Saves the data from the child to the main context
            try self.coreDataStack.managedContext.save()

            self.activitySpinner.stopAnimating()

            // Transition to the next screen
            let vc: IntroViewController = self.storyboard?.instantiateViewController(withIdentifier: "IntroScreen") as! IntroViewController
            vc.rootVCReference = self
            vc.coreDataStack = self.coreDataStack
            self.present(vc, animated:true, completion:nil)

        } catch {
            fatalError("Failure to save context: \(error)")
        }
    }
  } catch {
    fatalError("Failure to save context: \(error)")
  }
}

在我的 CoreDataStack 类中:

func importDefaultData(childContext: NSManagedObjectContext) {
  // import data into Core Data here, code not shown for brevity

  saveChildContext(childContext: childContext)

  // import more data into Core Data here, code not shown for brevity

  saveChildContext(childContext: childContext)

  // import final data here

  saveChildContext(childContext: childContext)
}

func saveChildContext(childContext: NSManagedObjectContext) {
    guard childContext.hasChanges else {
        return
    }

    do {
        try childContext.save()
    } catch {
        let nserror = error as NSError
    }
}

如果这种方法不是最佳做法,或者任何人都可以找到改进它的方法,我会很感激你的想法。我应该补充一点,我发现以下链接非常有帮助:https://marcosantadev.com/coredata_crud_concurrency_swift_1

【讨论】:

  • 为什么要保存3次?保存子上下文后,更改将在父上下文中可用,但您需要保存父上下文才能将其写入 SQLite 数据库文件。如果您不保存父上下文并且用户终止了您的应用,那么更改将会丢失。
  • context.perform { } 表示 { } 中的代码将在适合该上下文的线程中执行。因此,当您处理私有队列中的子上下文时,您不应直接进行任何 UI 更新(例如停止微调器),因为 UI 内容需要在主队列中完成,并且代码块将在私有队列中执行.使用 DispatchQueue.main.async { // UI 东西 }
  • context.perform { } 和 context.performAndWait { } 的区别在于 performAndWait 会阻塞当前线程,直到它完成执行。因此,如果您使用 perform 下面的语句 { } 将在 { } 的内容执行之前执行。
  • 更简洁的方法是构建操作(以前称为 NSOperation)。这样,您可以将多个操作链接在一起并具有依赖关系。它需要你阅读它。学习需要时间,但在实际应用中会有所帮助
  • 假设如果你在一个上下文中进行 10 次插入和 2 次删除和 1 次更新,你不必保存 10 次。保存一次将保存所有插入/更新/删除。阅读这个概念(在前面提供的链接中)将帮助您更好地理解
猜你喜欢
  • 1970-01-01
  • 2011-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多