Contents

CoreData Notifications With Swift


After the series about CRUD operations, now it’s time to unveil the last Core Data beast: Notifications.

Introduction

As you may have noticed, I’ve written several articles about Core Data recently. I wanted to study this framework well to publish a new open source library, StorageKit. Then, I thought to share with the community my knowledge because I think it’s very important to know how to take advantage of all the Core Data features.

In this article, we’ll see how to use the notifications provided by Core Data.

Happy reading!

Notifications

Core data provides the possibility to observer different notifications which we receive when something changes in our Core Data environment.

Let’s see what notifications we can observer and how to use them:

NSManagedObjectContextObjectsDidChange

CoreData sends this notification when we perform any CRUD operations with a NSManagedObject.

Note:

  • We don’t need to save the changes in the context to receive this notification. Once we perform a CRUD operation, we’ll receive this notification. For example, if we create a new object like this:

    1
    2
    
    let test = Test(context: persistentContainer.viewContext)
    test.name = "Hello World"
    
    1
    2
    3
    
    
     we'll receive the notification even though we didn't save the context.
    
    
  • Since we receive this notification only when we change manually a NSManagedObject, we don’t trigger it if we execute NSBatchUpdateRequest/NSBatchDeleteRequest.

NSManagedObjectContextWillSave

CoreData sends this notification when we are going to save a context with the method save of NSManagedObjectContext. It may be useful if we have to perform some actions before keeping the persistence of our changes.

NSManagedObjectContextDidSave

This notification is very similar to NSManagedObjectContextWillSave. The only difference is that CoreData sends it after saving the context.

Usage

We can observer these notifications adding an observer to NotificationCenter like this:

1
2
3
4
5
NotificationCenter.default.addObserver(self, selector: #selector(contextObjectsDidChange(_:)), name: Notification.Name.NSManagedObjectContextObjectsDidChange, object: nil)

NotificationCenter.default.addObserver(self, selector: #selector(contextWillSave(_:)), name: Notification.Name.NSManagedObjectContextWillSave, object: nil)

NotificationCenter.default.addObserver(self, selector: #selector(contextDidSave(_:)), name: Notification.Name.NSManagedObjectContextDidSave, object: nil)

And the selectors are like these:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func contextObjectsDidChange(_ notification: Notification) {
	print(notification)
}

func contextWillSave(_ notification: Notification) {
	print(notification)
}

func contextDidSave(_ notification: Notification) {
	print(notification)
}

The object Notification stores also the context where we changed the NSManagedObject (for NSManagedObjectContextObjectsDidChange), where we are going to save (for NSManagedObjectContextWillSave) and where we saved (NSManagedObjectContextDidSave).

We can read the context like this:

1
let context = notification.object as? NSManagedObjectContext

Keys

The notifications are just half of the whole story. Inside the userInfo of the Notification object, Core Data provides also some Sets with the information of what has been changed.

Let’s see the list of keys which we can use to retrieve the data changed:

NSInsertedObjectsKey

This is the key for a Set which contains all the NSManagedObjects inserted.

If we observe NSManagedObjectContextObjectsDidChange, the Set of this key contains the object which we create like in this example:

1
2
3
// Triggers `NSManagedObjectContextObjectsDidChange` with `test` as inserted object
let test = Test(context: persistentContainer.viewContext)
test.name = "Hello World"

NSUpdatedObjectsKey

This is the key for a Set which contains all the NSManagedObjects updated.

NSDeletedObjectsKey

This is the key for a Set which contains all the NSManagedObjects deleted.

NSRefreshedObjectsKey

This is the key for a Set which contains all the NSManagedObjects refreshed.

Refreshing objects

When we perform any CRUD operations in a NSManagedObject, until we save the context, these changes remain in pending without affecting the data in the database. If we want to discard any changes not saved yet, we can refresh all these pending NSManagedObjects like this:

1
myContext.refreshAllObjects()

Or we can also refresh the single object like this:

1
myContext.refresh(myObject, mergeChanges: false)

You can find more details about this function in the official documentation.

NSInvalidatedObjectsKey

This is the key for a Set which contains all the NSManagedObjects invalidated.

NSInvalidatedAllObjectsKey

This is the key for a boolean value which specifies if all the NSManagedObjects in the context have been invalidated.

Usage

We can use these keys in the userInfo dictionary of the object Notification like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func observerSelector(_ notification: Notification) {

	if let insertedObjects = notification.userInfo?[NSInsertedObjectsKey] as? Set<NSManagedObject>, !insertedObjects.isEmpty {
		print(insertedObjects)
	}

	if let updatedObjects = notification.userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObject>, !updatedObjects.isEmpty {
		print(updatedObjects)
	}

	if let deletedObjects = notification.userInfo?[NSDeletedObjectsKey] as? Set<NSManagedObject>, !deletedObjects.isEmpty {
		print(deletedObjects)
	}

	if let refreshedObjects = notification.userInfo?[NSRefreshedObjectsKey] as? Set<NSManagedObject>, !refreshedObjects.isEmpty {
		print(refreshedObjects)
	}

	if let invalidatedObjects = notification.userInfo?[NSInvalidatedObjectsKey] as? Set<NSManagedObject>, !invalidatedObjects.isEmpty {
		print(invalidatedObjects)
	}

	if let areInvalidatedAllObjects = notification.userInfo?[NSInvalidatedAllObjectsKey] as? Bool {
		print(areInvalidatedAllObjects)
	}
}

Note

If we use the notification NSManagedObjectContextWillSave, we won’t have data for these keys since CoreData doesn’t know what has been saved yet.

Use Case

As we saw in the previous series about Core Data and concurrency, if we don’t use the persistent container and we want to save a private NSManagedObjectContext, then we must save the main NSManagedObjectContext manually. With the notifications, we can avoid this manual save call for the main context.

In the following example, we can see how I used the notifications in StorageKit to keep the main context always updated:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Main context
let mainManagedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)

// Observers when a context has been saved
NotificationCenter.default.addObserver(self,
                                       selector: #selector(self.contextSave(_ :)),
                                       name: NSNotification.Name.NSManagedObjectContextDidSave,
                                       object: nil)


func contextSave(_ notification: Notification) {
	// Retrieves the context saved from the notification 
	guard let context = notification.object as? NSManagedObjectContext else { return }

	// Checks if the parent context is the main one
	if context.parent === mainManagedObjectContext {
	
		// Saves the main context
		mainManagedObjectContext.performAndWait {
			do {
				try mainManagedObjectContext.save()
			} catch {

			}
		}
	}
}

To take advantage of this approach, we must have a class which contains our Core Data stack and which observers the notifications. Have a look at CoreDataStorage for more details.

Conclusion

That’s all for the notifications of Core Data. I hope these articles are useful for all of you who are using or want to use Core Data in own applications. I’ll write another article—and probably the last one—about Core Data where I’ll show how to mock it for the unit test.

I’m going on holiday for a couple of weeks, therefore I’ll publish the next article at the end of August.

Enjoy the summer ☀️