caziz caziz - 1 year ago 41
Swift Question

Core Data and Concurrency

I am using the

performBackgroundTask
function to pull data from firebase, compare it with data already stored in Core Data, save new data to Core Data, and call a completion handler when done.

I understand that Core Data is not thread safe but I am trying to do this concurrently.

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)")
}
}
}

}


Fetch from Core Data function:

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. Can I fetch Core Data from the main thread during
    performBackgroundTask
    ?

  2. Should I filter with the high level
    filter
    function or using a special batch request (can I do that concurrently?)

  3. How can I use
    dispatchGroup.notify(queue:)
    to determine when the creation and saving of Core Data is complete?


Answer Source

Can I fetch Core Data from the main thread during performBackgroundTask?

You can fetch from any thread if you use that method. You can't use the results on the main thread though. NSPersistentContainer provides the viewContext property for use on the main thread.

Should I filter with the high level filter function or using a special batch request (can I do that concurrently?)

I'd do it with a predicate on a normal non-batch request. Either of the ways you mention are possible. It depends on what kind of fetching and filtering you need. A batch request might be good if the fetch and filter takes a long time to run. Filtering the results after the fetch might be good if your filtering rules can't be expressed in a predicate.

How can I use dispatchGroup.notify(queue:) to determine when the creation and saving of Core Data is complete?

Add the notify call after your forEach closure. If you never enter, it'll execute immediately. If you do enter, it will execute when you match each enter with a leave.

One other detail: Your getSaved method should take a managed object context as an argument, and fetch with that context. Otherwise you're mixing contexts here. The performBackgroundTask creates one context, but you're using a different one in getSaved.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download