Saving Settings with Core Data Metadata

November 6th, 2017

Filed under: Cocoa, iOS Development, Mac Development | Be the first to comment!

I’m working on a Core Data app where I needed to save some document settings. Creating Core Data entities for these settings was overkill. In my research for a solution I discovered persistent store metadata. I did not find much information online on using persistent store metadata so I’m sharing what I learned in this article.

This article is not for people new to Core Data. If you’re new to Core Data, start by reading Core Data Programming Guide, which is part of Apple’s developer documentation.

Persistent Store Metadata

Core Data uses persistent stores to save data. While you can use an in-memory store, most persistent stores are files. In a document-based Core Data app the document file contains the persistent store.

Persistent stores have a metadata property to store metadata. The metadata is a dictionary. The dictionary keys are strings, and the dictionary values can be any data type. Core Data stores the store type and unique identifier in the metadata. You can also add your own entries to the metadata to store small pieces of data that don’t belong in a Core Data entity.

The most common reason for someone to use persistent store metadata is to save per-document settings in Core Data apps that use NSPersistentDocument. Most people who use Core Data have no need to deal with the metadata. They can stick with Core Data entities and Apple’s UserDefaults class. But there are edge cases where saving data in the persistent store’s metadata makes sense.

Getting the Persistent Store

The persistent store contains the metadata. If you want to access the metadata, you must access the persistent store. The managed object context has a persistent store coordinator. The coordinator contains an array of persistent stores. The store is the first element in the array.

let store = context.persistentStoreCoordinator?.persistentStores.first

Keep in mind the array of persistent stores can be empty. If you create a new document in a document-based Core Data application, a persistent store is not created until you save the document. Check that the persistentStores array is not empty before accessing its elements.

Updating the Metadata

Once you have access to the persistent store, take the following steps to fill the metadata:

  1. Create a variable for the metadata.
  2. Set the variable to the store’s metadata property.
  3. Add dictionary entries for your fields.
  4. Set the store’s metadata property to your variable.

Core Data saves some internal data in the metadata. You should be adding your fields to the existing metadata, not overwriting the existing metadata with your fields. That’s why you create a variable for the metadata and set its initial value to the store’s existing metadata before you add any fields to the metadata.

The following Swift code demonstrates how to update the metadata to store the author of a document:

class Document: NSPersistentDocument {
    // Initial value for author. You would set the author's
    // value from a text field in your app's user interface.
    var author = ""
}

if let store = context.persistentStoreCoordinator?.persistentStores.first {
    let metadata = fillMetadata(store: store)
    store.metadata = metadata
}

func fillMetadata(store: NSPersistentStore) -> [String: Any] {
    var metadata: [String: Any] = store.metadata
    metadata["Author"] = author
    return metadata
}

After updating the metadata, save the managed object context to save the metadata. If you stick with simpler data types like strings and integers, you can save without having to write any more code. If you try to save something more complicated, such as an enum, you will have to write code to conform to the Codable protocol or your app will crash when you save.

NSPersistentStoreCoordinator also has class and instance methods, both named setMetadata, to set the metadata. I went with the simplest solution, setting the store’s metadata property.

When to Update the Metadata?

A good time to update the metadata is when you’re about to save the managed object context. Register for the notification NSManagedObjectContextWillSave.

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

Handle the notification by calling your function to update the metadata.

func contextWillSave(_ aNotification: Notification) {
    updateMetadata()
}

Retrieving the Metadata

Call the NSPersistentStoreCoordinator instance method metadataForPersistentStore to retrieve the metadata for your persistent store. Supply the persistent store as the argument to metadataForPersistentStore.

The following Swift code demonstrates how to retrieve the document’s author from the metadata:

if let store = context.persistentStoreCoordinator?.persistentStores.first,
    let metadata = context.persistentStoreCoordinator?.metadata(for: store) {        
        fillSettings(metadata: metadata)
}

func fillSettings(metadata: [String: Any]) {
    if let theAuthor = metadata["Author"] as? String {
        author = theAuthor
    }
}

NSPersistentStoreCoordinator also has a class method to retrieve the metadata, but it requires you to write a do-try-catch block. Calling the instance method is easier.

Tags:


Leave a Reply

Your email address will not be published. Required fields are marked *