How to call an update on the main thread - ios

I have a private NSManagedObjectContext queue that I'm using to save an entity to Core Data. After it has finished saving, I want to send out an NSNotification. However, it doesn't seem to like me sending out the notification from the private queue. This is my code for the private queue:
let parentManagedContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext!
let privateManagedContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateManagedContext.persistentStoreCoordinator = parentManagedContext.persistentStoreCoordinator
privateManagedContext.performBlock {
...
// Save the entity
do {
try privateManagedContext.save()
// Send out NSNotification here
}
}
How do I add a block within performBlock to run on the main thread?

Ok so minutes after I posted this question I figured out the answer. All I had to do was add this code after the try privateManagedContext.save() code:
NSOperationQueue.mainQueue().addOperationWithBlock({
NSNotificationCenter.defaultCenter().postNotificationName(kNotificationName, object: nil)
})

Hope this helps
dispatch_async(dispatch_get_main_queue()) {
NSNotificationCenter.defaultCenter().postNotificationName(kNotificationName, object: nil)
}

Related

Coredata NSFetchResultControllerDelegate didChange called but anObject changedValues() is empty

I'm facing a NSFetchResultController behaviour difference given two different coredata managed object context stack.
First scenario: coredata stack is nested, i.e. mainContext is parent of all background contexts created for each of our tasks managing our data. The main context's parent is a separate background context just for saving on background thread the main context and avoid blocking the ui.
The main context is created as following in this nested case:
let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)
// We create 2 contexts: one private to save in persistentStore, on a background thread, and one main. See [this design](https://cocoacasts.com/core-data-and-concurrency/) for more details.
persistentStoreContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
persistentStoreContext.persistentStoreCoordinator = psc
persistentStoreContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
mainContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
mainContext.parent = persistentStoreContext
The background context is created as following in this nested case:
let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
context.parent = mainContext
I have background task that owns background Coredata context in which I modify some entity then I save the context so that change is propagated to main given the parent relationship. In this case I get a didChange called on my NSFetchResultController delegate with "update" change key and a non empty changedValues() from the object that has been actually changed from my background context.
public func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .delete:
break
case .insert:
break
case .move:
break
case .update:
if let channel = anObject as? Channel,
let channelVM = channelViewModels[channel.epgId]?.viewModel {
if channel.changedValues()["lastImportStartDate"] != nil {
channelVM.resetFRC()
channelVM.viewModelShouldUpdateData(self)
}
So far so good, I can detect what has been changed and decide if I need to reload my ui.
Failing second scenario: stack context is sibling, both main and background context have coordinator for parent hence no relation between contexts.
The main context is created as following in this sibling case:
let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)
mainContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
The private context for our background tasks is created as following in this sibling case:
let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
On my background task I modify a modeled property (not transient) then I save the context that propagates change to store with the following code :
open func saveWithSiblingStack(completion: #escaping ((Result<Bool, Error>) -> Void)) {
guard let context = context else {
completion(Result.success(true)) // leads to didFinish
return
}
// If sibling stack is used, mainContext is not
// set to automaticallyMergeFrom parent as we handle this
// below to make sure didFinish is called after maincontext having
// merged changes from this task background context
context.perform {
if context.hasChanges {
let observer = NotificationCenter.default.addObserver(forName: .NSManagedObjectContextDidSave, object: context, queue: .main) { notification in
self.dataController.mainContext.performAndWait {
self.dataController.mainContext.mergeChanges(fromContextDidSave: notification)
}
self.queue.async {
completion(Result.success(true))
}
}
do {
try context.save()
} catch {
completion(Result.failure(error))
}
NotificationCenter.default.removeObserver(observer)
} else {
completion(Result.success(true))
}
}
}
In this configuration, my NSFetchResultController delegate is called with a didChange "update" but the changedValues() array is empty.
Does someone knows why in that case I don't get the detail of what has been changed from the background context ? This is especially weird knowing that if I subscribe to the merge notification manually (i.e. NotificationCenter.default.addObserver(forName: .NSManagedObjectContextDidMergeChangesObjectIDs, object: nil, queue: .main)) in the same class holding my fetch result controller I actually get the detail of what has been change from the merge via the notification.userInfo?[NSUpdatedObjectsKey] dic and changedValues() is not empty !
let observer = NotificationCenter.default.addObserver(forName: .NSManagedObjectContextDidMergeChangesObjectIDs, object: nil, queue: .main) { notification in
if let objects = notification.userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObject> {
for obj in objects {
guard let channel = obj as? Channel else { continue }
if obj.changedValues()["lastImportStartDate"] != nil {
// here contrary to NSFetchResultController, obj.changedValues() is not empty with the sibling stack
Of course I searched stackoverflow for similar case without success and apple developer forum.
I tested different NSFetchResultController options (with, without cache) and fetch request options as well (includePendingChange ...) but I always get changesValues() empty.
Many thanks in advance for any help or link to external resources I may have not already seen.
Best regards
Cédric

Performing selector on Main thread from the current thread inside performAndWait block of a NSManagedObjectContext

I have an NSManagedObjectContext which is initialised from newBackgroundContext of the persistentContainer as following:
managedContext = coreDataStack.persistentContainer.newBackgroundContext()
This is how persistentContainer looks like:
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "myXCDataModelName")
container.loadPersistentStores(completionHandler: { [weak self] (_, error) in
if let self = self,
let error = error as NSError? {
print("error!")
}
})
return container
}()
I'm using newBackgroundContext to make sure any CRUD operation with CoreData can be done safely, regardless what thread attempts to make changes on the managedContext, instead of making sure or forcing each operation is done on main thread.
I have a saveContext method where I try to perform save operation on managedContext inside performAndWait block as following:
managedContext.performAndWait {
do {
guard UIApplication.shared.isProtectedDataAvailable,
managedContext.hasChanges else {
return
}
try managedContext.save()
} catch {
print("error!")
}
}
It looks like performAndWait is most of the time runs on main thread but when it is run on another thread, the thread checker generates a warning for following check UIApplication.shared.isProtectedDataAvailable since it should be done on main thread.
I decided to run a selector on main thread for this check so I declared a isProtectedDataAvailable Bool by defaulting it to false at class level and then update its value when this selector runs.
#objc private func checker() {
isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
}
Now I refactored the performAndWait block as following to run the check on main thread if it is called from another thread.
managedContext.performAndWait {
do {
if Thread.isMainThread {
checker()
} else {
print("RUNNING ON ANOTHER THREAD")
Thread.current.perform(#selector(checker),
on: Thread.main,
with: nil,
waitUntilDone: true,
modes: nil)
}
guard isProtectedDataAvailable,
managedContext.hasChanges else {
return
}
try managedContext.save()
} catch {
print("error!")
}
}
It seems to be working fine when I run it on simulator or real device, I generate different core data related operations which would trigger saving context both on main and background threads.
But what happens is, if I put some breakpoints inside performAndWait block and stop execution to examine how the code block is working, it sometimes results in application freeze when I continue execution, like a deadlock occurs. I wonder if it is somehow related to stopping execution with breakpoints or something is wrong with my implementation even though it is working fine without breakpoints, no app freeze or anything.
I'm worried because before going with this solution, I tried synchronizing on main thread by the inspiration from this answer to just switch to main thread to make this check, as following (which resulted in app freeze and I assume a deadlock) inside performAndWait block:
var isProtectedDataAvailable = false
if Thread.isMainThread {
isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
} else {
DispatchQueue.main.sync {
isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
}
}
I had to use sync instead of async, because I had to retrieve updated value of isProtectedDataAvailable before proceeding execution.
Any ideas on this?
I assume your question is about the right approach so that the code is working fine and it allows to set any breakpoints without running into deadlocks. :)
Why not try it this way (from what you stated i cannot see reasons against it):
First evaluating UIApplication.shared.isProtectedDataAvailable on the main thread. (I guess that there are more complex conditions that must hold not only UIApplication.shared.isProtectedDataAvailable = true, but to keep it simple ...)
If UIApplication.shared.isProtectedDataAvailable (and otherConditions) evaluates as true continue with data processing and saving on background thread. As managedContext was created as a newBackgroundContext it can be used there.
Instructions on main thread
if UIApplication.shared.isProtectedDataAvailable && otherConditions {
DispatchQueue.global(qos: .userInitiated).async {
dataProcessing()
}
}
Instructions on background thread
static func dataProcessing() {
// get the managedContext
let context = AppDelegate.appDelegate.managedContext
context.performAndWait {
if context.hasChanges {
do {
try context.save()
} catch {
print("error!")
}
}
}
}
For some reason, moving the following check and synchronising with main queue outside of the performAndWait solves all the problems.
No deadlock or app freeze occurs either with break points or without, regardless which thread triggers the method which contains performAndWait
So the method body looks like following now:
var isProtectedDataAvailable = false
if Thread.isMainThread {
isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
} else {
DispatchQueue.main.sync {
isProtectedDataAvailable = UIApplication.shared.isProtectedDataAvailable
}
}
managedContext.performAndWait {
do {
guard isProtectedDataAvailable,
managedContext.hasChanges else {
return
}
try managedContext.save()
} catch {
print("error")
}
}

CoreData: how to create and save objects inside `NSManagedObjectContextObjectsDidChange`?

I would like to react to modified NSManagedObjects, therefore I setup an observer:
NotificationCenter.default.addObserver(forName: NSNotification.Name.NSManagedObjectContextObjectsDidChange, object: nil, queue: nil) { notification in
...
}
But I didn't find a solution yet how to create objects inside that block.
NotificationCenter.default.addObserver(forName: NSNotification.Name.NSManagedObjectContextObjectsDidChange, object: nil, queue: nil) { notification in
let context = notification.object as! NSManagedObjectContext
context.perform {
let insertedObjects = notification.userInfo?[NSInsertedObjectsKey] as? Set<NSManagedObject> ?? Set<NSManagedObject>()
// insertedObjects are empty (outside of context.perform they are NOT EMPTY
}
}
Also when I do not use context.perform I do get attempt to recursively call -save: on the context aborted. How can I achieve this?

Realm notification token on background thread

I was trying to fetch realm data on the background thread and add a notification block (iOS, Swift).
Basic example:
func initNotificationToken() {
DispatchQueue.global(qos: .background).async {
let realm = try! Realm()
results = self.getRealmResults()
notificationToken = results.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in
switch changes {
case .initial:
self?.initializeDataSource()
break
case .update(_, let deletions, let insertions, let modifications):
self?.updateDataSource(deletions: deletions, insertions: insertions, modifications: modifications)
break
case .error(let error):
fatalError("\(error)")
break
}
}
}
}
func initializeDataSource() {
// process the result set data
DispatchQueue.main.async(execute: { () -> Void in
// update UI
})
}
func updateDataSource(deletions: [Int], insertions: [Int], modifications: [Int]) {
// process the changes in the result set data
DispatchQueue.main.async(execute: { () -> Void in
// update UI
})
}
When doing this I get
'Can only add notification blocks from within runloops'
I have to do some more extensive processing with the returned data and would like to only go back to the main thread when updating the UI after the processing is done.
Another way would probably to re-fetch the data after any update on the background thread and then do the processing, but it feels like avoidable overhead.
Any suggestions on the best practice to solve this?
To add a notification on a background thread you have to manually run a run loop on that thread and add the notification from within a callout from that run loop:
class Stuff {
var token: NotificationToken? = nil
var notificationRunLoop: CFRunLoop? = nil
func initNotificationToken() {
DispatchQueue.global(qos: .background).async {
// Capture a reference to the runloop so that we can stop running it later
notificationRunLoop = CFRunLoopGetCurrent()
CFRunLoopPerformBlock(notificationRunLoop, CFRunLoopMode.defaultMode.rawValue) {
let realm = try! Realm()
results = self.getRealmResults()
// Add the notification from within a block executed by the
// runloop so that Realm can verify that there is actually a
// runloop running on the current thread
token = results.addNotificationBlock { [weak self] (changes: RealmCollectionChange) in
// ...
}
}
// Run the runloop on this thread until we tell it to stop
CFRunLoopRun()
}
}
deinit {
token?.stop()
if let runloop = notificationRunLoop {
CFRunLoopStop(runloop)
}
}
}
GCD does not use a run loop on its worker threads, so anything based on dispatching blocks to the current thread's run loop (such as Realm's notifications) will never get called. To avoid having notifications silently fail to do anything Realm tries to check for this, which unfortunately requires the awakward PerformBlock dance.

Swift CoreData: error: Serious application error. Exception was caught during Core Data change processing.

I am writing one program on iOS and very race I am facing this error:
2015-11-06 10:57:24.289 NETFNET[2503:976392] CoreData: error: Serious application error. Exception was caught during Core Data change processing. This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification. -[__NSCFSet addObject:]: attempt to insert nil with userInfo (null)
2015-11-06 10:57:24.293 NETFNET[2503:976392] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFSet addObject:]: attempt to insert nil'
I am trying to access Data Base simultaneously, I think, from main and background threads. I have seen a lot of solutions for Objective C, but none for Swift (I don't know Objective C...). Unfortunately, I don't know how to work with Grand Central Dispatch and, in fact, my program does not really need several treads (I mean it need it, but if some thread loses info from one function for one time, nothing bad will happen). I just want to have stable program on Swift 1 or 2, so I will be thankful for any help.
You need to create a private NSManagedObjectContext with private queue concurrency type and use it to access CoreData whenever operating on a background thread.
So suppose I need to run a database operation on the background, I can dispatch that work to the background thread as
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), {
//call your background operation.
})
Then in the background operation I can create a private NSManagedObjectContext as
let moc = … //Our primary context on the main queue
let privateMOC = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateMOC.parentContext = moc
privateMOC.performBlock {
//operations
do {
try privateMOC.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
Read through Apple's CoreData Concurrency Guide to get a good understanding before implementing core data operations on multiple threads.
Very good. I tried it, it worked fine for me.
Thank you very much.
Previous Code:
do {
try CDHelper.shared.context.save()
}
catch let error as NSError {
// Error mesajlarını ekle!!
print("Could not fetch \(error), \(error.localizedDescription)")
print("Could not fetch \(error), \(error.localizedFailureReason)")
}
// MARK: - CONTEXT
lazy var context: NSManagedObjectContext = {
let moc = NSManagedObjectContext(concurrencyType:.MainQueueConcurrencyType)
moc.persistentStoreCoordinator = self.coordinator
return moc
}()
// MARK: - MODEL
lazy var model: NSManagedObjectModel = {
return NSManagedObjectModel(contentsOfURL:self.modelURL)!
}()
// MARK: - COORDINATOR
lazy var coordinator: NSPersistentStoreCoordinator = {
return NSPersistentStoreCoordinator(managedObjectModel:self.model)
}()
lazy var modelURL: NSURL = {
let bundle = NSBundle.mainBundle()
if let url = bundle.URLForResource("Model", withExtension: "momd") {
return url
}
print("CRITICAL - Managed Object Model file not found")
abort()
}()
you should change the code this way:
let moc = NSManagedObjectContext(concurrencyType:.MainQueueConcurrencyType)
let privateMOC = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateMOC.parentContext = moc
privateMOC.performBlock({
do {
try privateMOC.save()
} catch {
fatalError("Failure to save context: \(error)")
}
})
Just calling your CoreData Function inside
DispatchQueue.main.async {
...
}
worked for me
You can create an extension for this and wrap the save() function to something like this so you'll just need to use this function instead of save():
extension NSManagedObjectContext {
func update() throws {
let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
context.parent = self
context.perform({
do {
try context.save()
} catch {
print(error)
}
})
}
}

Resources