【问题标题】:Core Data and Concurrency核心数据和并发
【发布时间】:2018-01-02 03:42:39
【问题描述】:

我正在使用performBackgroundTask 函数从firebase 中提取数据,将其与已存储在Core Data 中的数据进行比较,将新数据保存到Core Data,并在完成后调用完成处理程序。

我了解 Core Data 不是线程安全的,但我正在尝试同时执行此操作。

static func cache(completion: @escaping (Void) -> Void) {
    CoreDataHelper.persistentContainer.performBackgroundTask { (context) in
        let dispatchGroup = DispatchGroup()
        // fetch previously saved Core Data from main thread (1) and filter them (2)
        let newsSourceIDs = NewsSourceService.getSaved().filter{$0.isEnabled}.map{$0.id!}
        let oldArticleURLs = ArticleService.getSaved().map{$0.url!}
        // create firebase database reference
        let ref = Database.database().reference()
        Constants.Settings.timeOptions.forEach { time in
            let timeRef = ref.child("time\(time)minutes")
            newsSourceIDs.forEach { newsSourceID in
                dispatchGroup.enter()
                // pull from Firebase Database
                timeRef.child(newsSourceID).observeSingleEvent(of: .value, with: { (snapshot) in
                        guard let newsSourceDict = snapshot.value as? [String: [String:String]] else {
                            return
                        }
                        newsSourceDict.values.forEach { articleDict in
                            dispatchGroup.enter()
                            if oldArticleURLs.contains(articleDict["url"]!) {
                                dispatchGroup.leave()
                                return
                            }
                            // create article entity with firebase data
                            let article = Article(context: context)
                            article.date = articleDict["date"]
                            article.source = newsSourceID
                            article.time = Int16(time)
                            article.title = articleDict["title"]
                            article.url = articleDict["url"]
                            article.urlToImage = articleDict["urlToImage"]
                            dispatchGroup.leave()
                        }
                    dispatchGroup.leave()
                })
            }
        }
        // when done, save and call completion handler (3)
        dispatchGroup.notify(queue: .main) {
            do {
                try context.save()
                completion()
            } catch {
                fatalError("Failure to save context: \(error)")
            }
        }
    }

}

从核心数据函数中获取:

static func getSaved() -> [Article] {
    let fetchRequest: NSFetchRequest<Article> = Article.fetchRequest()
    do {
        let results = try CoreDataHelper.managedContext.fetch(fetchRequest)
        return results
    } catch let error as NSError {
        print("Could not fetch \(error)")
    }
    return []
}
  1. 我可以在performBackgroundTask 期间从主线程获取核心数据吗?
  2. 我应该使用高级filter 函数过滤还是使用特殊的批处理请求(我可以同时进行吗?)
  3. 如何使用dispatchGroup.notify(queue:) 来确定Core Data 的创建和保存何时完成?

【问题讨论】:

    标签: ios json firebase core-data swift3


    【解决方案1】:

    我可以在 performBackgroundTask 期间从主线程获取核心数据吗?

    如果您使用该方法,您可以从任何线程获取。但是,您不能在主线程上使用结果。 NSPersistentContainer 提供了viewContext 属性供主线程使用。

    我应该使用高级过滤功能还是使用特殊的批处理请求进行过滤(我可以同时进行吗?)

    我会在正常的非批处理请求上使用谓词。您提到的任何一种方式都是可能的。这取决于您需要什么样的获取和过滤。如果 fetch 和 filter 需要很长时间才能运行,那么批处理请求可能会很好。如果您的过滤规则无法在谓词中表达,那么在获取后过滤结果可能会很好。

    如何使用 dispatchGroup.notify(queue:) 来确定 Core Data 的创建和保存何时完成?

    forEach 关闭之后添加通知调用。如果您从未enter,它将立即执行。如果您执行enter,它将在您将每个enterleave 匹配时执行。

    另一个细节:您的getSaved 方法应该将托管对象上下文作为参数,并使用该上下文进行获取。否则,您将在这里混合上下文。 performBackgroundTask 创建了一个上下文,但您在 getSaved 中使用了不同的上下文。

    【讨论】:

    • context.save() 是否同步发生(或足够快)以使完成处理程序能够访问数据?
    • 应该通知使用哪个队列(不是 .main?)
    • 保存是同步的。通知哪个队列取决于通知块中的代码需要做什么。如果它正在更新 UI,则主队列。
    【解决方案2】:

    使用 Core Data 处理并发的另一种方法(最简单但未优化的方法之一)是使用 concurrencyType 为 private 的“子”managedObjectContext,将新的MOC 的父级设置为MOC在你的主线程上。

    let privateMOC = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
    
    privateMOC.parent = persistentManager.managedObjectContext
    
    privateMOC.perform {
        do {
            try privateMOC.save()
        } catch let error as NSError {
        }
    }
    

    您将在 .perform 闭包内执行所有需要的核心数据操作。当您运行 privateMOC.save() 时,更改会被推送到主线程上的父 managedObjectContext。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-01
      • 1970-01-01
      • 2015-03-20
      • 2012-06-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多