An Introduction to Core Data and SwiftUI

A common requirement when developing iOS apps is to store data in some form of structured database. One option is to directly manage data using an embedded database system such as SQLite. While this is a perfectly good approach for working with SQLite in many cases, it does require knowledge of SQL and can lead to some complexity in terms of writing code and maintaining the database structure. This complexity is further compounded by the non-object-oriented nature of the SQLite API functions. In recognition of these shortcomings, Apple introduced the Core Data Framework. Core Data is essentially a framework that places a wrapper around the SQLite database (and other storage environments) enabling the developer to work with data in terms of Swift objects without requiring any knowledge of the underlying database technology.

We will begin this chapter by defining some of the concepts that comprise the Core Data model before providing an overview of the steps involved in working with this framework. Once these topics have been covered, the next chapter will work through a SwiftUI Core Data tutorial.

The Core Data Stack

Core Data consists of several framework objects that integrate to provide the data storage functionality. This stack can be visually represented as illustrated in Figure 43-1:

Figure 43-1

As we can see from Figure 43-1, the app sits on top of the stack and interacts with the managed data objects handled by the managed object context. Of particular significance in this diagram is the fact that although the lower levels in the stack perform a considerable amount of the work involved in providing Core Data functionality, the application code does not interact with them directly.

 

You are reading a sample chapter from SwiftUI Essentials – iOS 15 Edition.

Buy the full book now in eBook (PDF, ePub, and Kindle) or Print format.

The full book contains 59 chapters and over 520 pages of in-depth information).

Learn more.

Preview  Buy eBook  Buy Print

 

Before moving on to the more practical areas of working with Core Data it is important to spend some time explaining the elements that comprise the Core Data stack in a little more detail.

Persistent Container

The persistent container handles the creation of the Core Data stack and is designed to be easily subclassed to add additional application-specific methods to the base Core Data functionality. Once initialized, the persistent container instance provides access to the managed object context.

Managed Objects

Managed objects are the objects that are created by your application code to store data. A managed object may be thought of as a row or a record in a relational database table. For each new record to be added, a new managed object must be created to store the data. Similarly, retrieved data will be returned in the form of managed objects, one for each record matching the defined retrieval criteria. Managed objects are instances of the NSManagedObject class, or a subclass thereof. These objects are contained and maintained by the managed object context.

Managed Object Context

Core Data-based applications never interact directly with the persistent store. Instead, the application code interacts with the managed objects contained in the managed object context layer of the Core Data stack. The context maintains the status of the objects in relation to the underlying data store and manages the relationships between managed objects defined by the managed object model. All interactions with the underlying database are held temporarily within the context until the context is instructed to save the changes, at which point the changes are passed down through the Core Data stack and written to the persistent store.

Managed Object Model

So far we have focused on the management of data objects but have not yet looked at how the data models are defined. This is the task of the Managed Object Model which defines a concept referred to as entities.

 

You are reading a sample chapter from SwiftUI Essentials – iOS 15 Edition.

Buy the full book now in eBook (PDF, ePub, and Kindle) or Print format.

The full book contains 59 chapters and over 520 pages of in-depth information).

Learn more.

Preview  Buy eBook  Buy Print

 

Much as a class description defines a blueprint for an object instance, entities define the data model for managed objects. In essence, an entity is analogous to the schema that defines a table in a relational database. As such, each entity has a set of attributes associated with it that define the data to be stored in managed objects derived from that entity. For example, a Contacts entity might contain name, address, and phone number attributes.

In addition to attributes, entities can also contain relationships, fetched properties, persistent stores, and fetch requests:

  • Relationships – In the context of Core Data, relationships are the same as those in other relational database systems in that they refer to how one data object relates to another. Core Data relationships can be one-to-one, one-to-many, or many-to-many.
  • Fetched property – This provides an alternative to defining relationships. Fetched properties allow properties of one data object to be accessed from another data object as though a relationship had been defined between those entities. Fetched properties lack the flexibility of relationships and are referred to by Apple’s Core Data documentation as “weak, one-way relationships” best suited to “loosely coupled relationships”.
  • Fetch request – A predefined query that can be referenced to retrieve data objects based on defined predicates. For example, a fetch request can be configured into an entity to retrieve all contact objects where the name field matches “John Smith”.

Persistent Store Coordinator

The persistent store coordinator is responsible for coordinating access to multiple persistent object stores. As an iOS developer, you will never directly interact with the persistent store coordinator and will very rarely need to develop an application that requires more than one persistent object store. When multiple stores are required, the coordinator presents these stores to the upper layers of the Core Data stack as a single store.

Persistent Object Store

The term persistent object store refers to the underlying storage environment in which data are stored when using Core Data. Core Data supports three disk-based and one memory-based persistent store. Disk-based options consist of SQLite, XML, and binary. By default, iOS will use SQLite as the persistent store. In practice, the type of store being used is transparent to you as the developer. Regardless of your choice of persistent store, your code will make the same calls to the same Core Data APIs to manage the data objects required by your application.

Defining an Entity Description

Entity descriptions may be defined from within the Xcode environment. When a new project is created with the option to include Core Data, a template file will be created named <entityname>.xcdatamodeld. Xcode also provides a way to manually add entity description files to existing projects. Selecting this file in the Xcode project navigator panel will load the model into the entity editing environment as illustrated in Figure 43-2:

 

You are reading a sample chapter from SwiftUI Essentials – iOS 15 Edition.

Buy the full book now in eBook (PDF, ePub, and Kindle) or Print format.

The full book contains 59 chapters and over 520 pages of in-depth information).

Learn more.

Preview  Buy eBook  Buy Print

 

Figure 43-2

Create a new entity by clicking on the Add Entity button located in the bottom panel. The new entity will appear as a text box in the Entities list. By default, this will be named Entity. Double-click on this name to change it.

To add attributes to the entity, click on the Add Attribute button located in the bottom panel, or use the + button located beneath the Attributes section. In the Attributes panel, name the attribute and specify the type and any other options that are required.

Repeat the above steps to add more attributes and additional entities.

The Xcode entity editor also allows relationships to be established between entities. Assume, for example, two entities named Contacts and Sales. To establish a relationship between the two tables select the Contacts entity and click on the + button beneath the Relationships panel. In the detail panel, name the relationship, specify the destination as the Sales entity, and any other options that are required for the relationship. Once the relationship has been established it is, perhaps, best viewed graphically by selecting the Table, Graph option in the Editor Style control located in the bottom panel:

 

You are reading a sample chapter from SwiftUI Essentials – iOS 15 Edition.

Buy the full book now in eBook (PDF, ePub, and Kindle) or Print format.

The full book contains 59 chapters and over 520 pages of in-depth information).

Learn more.

Preview  Buy eBook  Buy Print

 

Figure 43-3

Initializing the Persistent Container

The persistent container is initialized by creating a new NSPersistentContainer instance, passing through the name of the model to be used, and then making a call to the loadPersistentStores method of that object as follows:

let persistentContainer: NSPersistentContainer
 
persistentContainer = NSPersistentContainer(name: "DemoData")
persistentContainer.loadPersistentStores { (storeDescription, error) in
    if let error = error as NSError? {
        fatalError("Container load failed: \(error)")
    }
}

Obtaining the Managed Object Context

Since many of the Core Data methods require the managed object context as an argument, the next step after defining entity descriptions often involves obtaining a reference to the context. This can be achieved by accessing the viewContext property of the persistent container instance:

let managedObjectContext = persistentContainer.viewContext

Setting the Attributes of a Managed Object

As previously discussed, entities and the managed objects from which they are instantiated contain data in the form of attributes. Once a managed object instance has been created as outlined above, those attribute values can be used to store the data before the object is saved. Assuming a managed object named contact with attributes named name, address and phone respectively, the values of these attributes may be set as follows before saving the object to storage:

contact.name = "John Smith" 
contact.address = "1 Infinite Loop" 
contact.phone = "555-564-0980"

Saving a Managed Object

Once a managed object instance has been created and configured with the data to be stored it can be saved to storage using the save() method of the managed object context as follows:

 

You are reading a sample chapter from SwiftUI Essentials – iOS 15 Edition.

Buy the full book now in eBook (PDF, ePub, and Kindle) or Print format.

The full book contains 59 chapters and over 520 pages of in-depth information).

Learn more.

Preview  Buy eBook  Buy Print

 

do {
    try viewContext.save()
} catch {
    let error = error as NSError
    fatalError("An error occured: \(error)")
}

Fetching Managed Objects

Once managed objects are saved into the persistent object store those objects and the data they contain will likely need to be retrieved. One way to fetch data from Core Data storage is to use the @FetchRequest property wrapper when declaring a variable in which to store the data. The following code, for example, declares a variable named customers which will be automatically updated as data is added to or removed from the database:

@FetchRequest(entity: Customer.entity(), sortDescriptors: [])
private var customers: FetchedResults<Customer>

The @FetchRequest property wrapper may also be configured to sort the fetched results. In the following example, the customer data stored in the customers variable will be sorted alphabetically in ascending order based on the name entity attribute:

@FetchRequest(entity: Customer.entity(), 
        sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)])
private var customers: FetchedResults<Customer>

Retrieving Managed Objects based on Criteria

The preceding example retrieved all of the managed objects from the persistent object store. More often than not only managed objects that match specified criteria are required during a retrieval operation. This is performed by defining a predicate that dictates criteria that a managed object must meet to be eligible for retrieval. For example, the following code configures a @FetchRequest property wrapper declaration with a predicate to extract only those managed objects where the name attribute matches “John Smith”:

@FetchRequest(
  entity: Customer.entity(),
  sortDescriptors: [],
  predicate: NSPredicate(format: "name LIKE %@", "John Smith")
) 
private var customers: FetchedResults<Customer>

The above example will maintain the customers variable so that it always contains the entries that match the specified predicate criteria. It is also possible to perform one-time fetch operations by creating NSFetchRequest instances, configuring them with the entity and predicate settings, and then passing them to the fetch() method of the managed object context. For example:

@State var matches: [Customer]?
let fetchRequest: NSFetchRequest<Product> = Product.fetchRequest()
 
fetchRequest.entity = Customer.entity()
fetchRequest.predicate = NSPredicate(
    format: "name LIKE %@", "John Smith"
)
 
matches = try? viewContext.fetch(fetchRequest)

Summary

The Core Data Framework stack provides a flexible alternative to directly managing data using SQLite or other data storage mechanisms. By providing an object-oriented abstraction layer on top of the data the task of managing data storage is made significantly easier for the SwiftUI application developer. Now that the basics of Core Data have been covered, the next chapter entitled “A SwiftUI Core Data Tutorial” will work through the creation of an example application.

 

You are reading a sample chapter from SwiftUI Essentials – iOS 15 Edition.

Buy the full book now in eBook (PDF, ePub, and Kindle) or Print format.

The full book contains 59 chapters and over 520 pages of in-depth information).

Learn more.

Preview  Buy eBook  Buy Print