Chapter 9.  Storing and Querying Data in Core Data

Now that you know what Core Data is, how it works, and how you can integrate it in your application, the time is right to figure out how to store and query data with it. Setting up the store was fairly straightforward, especially with the new NSPersistentContainer that was introduced in iOS 10.

Handling actual data is a bit more complex since we might have to deal with things, such as multithreading or objects, that suddenly get updated while we've just read them from the database. Dealing with databases isn't easy, especially if you're trying to ensure data integrity. Luckily for us, Core Data helps tremendously with that.

In this chapter, we will insert some pieces of data into our database and read it. You'll learn how to update the user interface whenever the underlying data changes. You will also learn how you can ensure that you don't accidentally try to read or write objects on the wrong thread and why it matters. To read and filter data, you will use predicates. Finally, you will learn about some of the ways iOS 10 helps in making sure that you always use the correct objects from your database.

In this chapter, we will cover the following topics:

  • Inserting data with Core Data
  • Reading data with a simple fetch request
  • Filtering data with predicates
  • Reacting to database changes

Inserting data with Core Data

The first step to implementing data persistence for your app is to make sure that you can store some data in the database. The models that we will store were already defined with Xcode's model editor. First, we'll take a look at what's involved in storing data, and then we'll implement the code that persists our models to the database. Finally, we'll improve our code by refactoring it to be more reusable.

Understanding data persistence

Whenever you want to persist a model with Core Data, you essentially insert a new NSManagedObject into an NSManagedObjectContext. Doing this does not immediately persist the model you created. It merely stages your object in the current NSManagedObjectContext. If you don't properly manage your managed objects and contexts, this is a potential source of bugs. For example, not persisting your managed objects will result in the disappearance of your data once you refresh the context.

If you want to properly save managed objects, you will need to tell the managed object context to persist its changes down to the persistent store coordinator. The persistent store coordinator will take care of persisting the data in the underlying SQLite database.

Extra care is required when you use multiple managed object contexts. If you insert an object in one managed object context and persist it, you will manually need to synchronize the changes between managed object contexts. Also, managed objects are not thread safe. This means that you will need to make sure that you create, access, and store a managed object on the same thread at all times. The managed object context has a helper method called perform(:_) to help you with this.

Inserting new objects, updating them, or adding relationships between objects should always be done using this helper method. The reason is that the helper method makes sure that all pieces of code in the closure you want to perform are executed on the same thread as the managed object context is on.

Now that we're aware of how data persistence works in Core Data, it's time to start implementing the code to store our family members and their favorite movies. We'll implement the family member persistence first. Then, we'll expand the app so we can safely add movies to family members.

Persisting your models

The first model we will persist is the family member model. The app is already set up with a form that asks for a family member name and a delegate protocol that will inform the FamilyMembersViewController whenever we want to create a new family member.

Not that we're not validating any of the input data; normally, you'd want to add some checks that make sure that we're not trying to insert an empty family member name. For now, we'll skip that because this type of validation isn't Core Data specific. Our persistence code will be added to the saveFamilyMember(withName:) method.

Add the following implementation to the FamilyMembersViewController; we'll go over it line by line after adding the code:

func saveFamilyMember(withName name: String) { 
    guard let moc = managedObjectContext 
        else { return } 
     
    moc.perform { 
        let familyMember = FamilyMember(context: moc) 
        familyMember.name = name 
         
        do { 
            try moc.save() 
        } catch { 
            moc.rollback() 
        } 
    } 
} 

The first thing the preceding code does is to make sure that the managedObjectContext is set; if we don't have a managed object context, we can't save our model. Next, we will use the perform(:_) method to make sure that the insertion of our model will happen on the correct thread.

Inside the perform(:_) method, we will create a new instance for the family member in the current context. This means that, for now, this family member will just live inside of the managed object context. It's not persisted yet, and other managed object contexts will not be able to reach it.

Next, we set the name for our family member to the name we received from the form and then we attempt to save the managed object context. If the saving fails, we would want to roll back the managed object context to the state it was in before we attempted to add a new object. We do this to avoid unexpected behavior, and since the save call failed, there must be something wrong with our created managed object context.

Now that we will store family members, let's set up the MoviesViewController so it can store movies for a family member. This code won't work right away because we can't tap any actual family members in the overview yet. We'll implement the required code anyway, and then we'll make it work right after we start reading data from our database.

The code to store movies for a family member is very similar to the code we wrote earlier. Before you implement the saving code, make sure that you conform MoviesViewController to MOCViewControllerType and import CoreData.

We also need a variable to hold a family member; add the following declaration:

var familyMember: FamilyMember? 

After doing this, add the following implementation for saveMovie(withName:):

func saveMovie(withName name: String) { 
    guard let moc = managedObjectContext, 
        let familyMember = self.familyMember 
        else { return } 
     
    moc.perform { 
        let movie = Movie(context: moc) 
        movie.name = name 
        familyMember.favoriteMovies = 
            familyMember.favoriteMovies?.adding(movie) 
         
        do { 
            try moc.save() 
        } catch { 
            moc.rollback() 
        } 
    } 
} 

The most important differences between adding the movie and the family member are highlighted. As you can see, we wrote a lot of boilerplate code, and we can make clever use of extensions and generics in Swift to avoid writing all these boilerplate code. Let's refactor our app a bit.

Refactoring the persistence code

Many iOS developers dislike the amount of boilerplate code that is always involved with using Core Data. Simply persisting an object requires you to repeat a lot of code, which can become quite a pain to write and maintain over time. The approach to refactoring the persistence code presented in the following examples is heavily inspired by the approach taken in the Core Data book written by Florian Kugler and Daniel Eggert. If you're interested in learning more about Core Data than what this book covers and if you'd like to see more of the clever ways they use to reduce the amount of boilerplate code, you should definitely pick up their book.

The first pattern we can discover in our boilerplate code is the following:

moc.perform { 
    // persistence code 
 
    do { 
        try moc.save() 
    } catch { 
        moc.rollback() 
    } 
} 

It would be great if we could write the following code to persist our data instead:

moc.persist { 
    // persistence code 
} 

This can be achieved by writing an extension for NSManagedObjectContext. Add a file called NSManagedObjectContext to the extensions folder, and add the following implementation:

import CoreData 
 
extension NSManagedObjectContext { 
    func persist(block: @escaping ()->Void) { 
        perform { 
            block() 
             
            do { 
                try self.save() 
            } catch { 
                self.rollback() 
            } 
        } 
    } 
} 

The preceding code enables us to reduce the amount of boilerplate code, which is something that we should always try to achieve, regardless of Core Data. Reducing boilerplate code greatly improves your code's readability and maintainability. Update both the family overview and the movie list view controllers to make use of this new persistence method.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.19.75