Core Data in Objective-C, Third Edition

Data Storage and Management for iOS and OS X

by Marcus S. Zarra

Version: P2.0 (July 2016)

Copyright © 2016 The Pragmatic Programmers, LLC. This book is licensed to the individual who purchased it. We don't copy-protect it because that would limit your ability to use it for your own purposes. Please don't break this trust—you can use this across all of your devices but please do not share this copy with other members of your team, with friends, or via file sharing services. Thanks.

Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and The Pragmatic Programmers, LLC was aware of a trademark claim, the designations have been printed in initial capital letters or in all capitals. The Pragmatic Starter Kit, The Pragmatic Programmer, Pragmatic Programming, Pragmatic Bookshelf and the linking g device are trademarks of The Pragmatic Programmers, LLC.

Every precaution was taken in the preparation of this book. However, the publisher assumes no responsibility for errors or omissions, or for damages that may result from the use of information (including program listings) contained herein.

About the Pragmatic Bookshelf

The Pragmatic Bookshelf is an agile publishing company. We’re here because we want to improve the lives of developers. We do this by creating timely, practical titles, written by programmers for programmers.

Our Pragmatic courses, workshops, and other products can help you and your team create better software and have more fun. For more information, as well as the latest Pragmatic titles, please visit us at http://pragprog.com.

Our ebooks do not contain any Digital Restrictions Management, and have always been DRM-free. We pioneered the beta book concept, where you can purchase and read a book while it’s still being written, and provide feedback to the author to help make a better book for everyone. Free resources for all purchasers include source code downloads (if applicable), errata and discussion forums, all available on the book's home page at pragprog.com. We’re here to make your life easier.

New Book Announcements

Want to keep up on our latest titles and announcements, and occasional special offers? Just create an account on pragprog.com (an email address and a password is all it takes) and select the checkbox to receive newsletters. You can also follow us on twitter as @pragprog.

About Ebook Formats

If you buy directly from pragprog.com, you get ebooks in all available formats for one price. You can synch your ebooks amongst all your devices (including iPhone/iPad, Android, laptops, etc.) via Dropbox. You get free updates for the life of the edition. And, of course, you can always come back and re-download your books when needed. Ebooks bought from the Amazon Kindle store are subject to Amazon's polices. Limitations in Amazon's file format may cause ebooks to display differently on different devices. For more information, please see our FAQ at pragprog.com/frequently-asked-questions/ebooks. To learn more about this book and access the free resources, go to https://pragprog.com/book/mzcd3, the book's homepage.

Thanks for your continued support,

Dave Thomas and Andy Hunt

The Pragmatic Programmers

The team that produced this book includes: Jacquelyn Carter (editor)   Potomac Indexing, LLC (indexer)   Liz Welch (copyeditor)   Gilson Graphics (layout)   Janet Furlow (producer)

For customer support, please contact support@pragprog.com.

For international rights, please contact rights@pragprog.com.

Table of Contents

1. Introduction

1. Is This Book for You?

2. What Is Core Data?

3. Online Resources

2. 1. Building a Foundation

1. The Storyboard

2. The Recipe List

3. The Recipe Detail

4. The Edit Workflow

5. Ready for Core Data

3. 2. Under the Hood of Core Data

1. Introducing the NSManagedObjectModel

2. Integrating with the NSPersistentStoreCoordinator

3. Adding the NSManagedObjectContext

4. Working with NSManagedObject Instances

5. Building an NSFetchRequest

6. NSSortDescriptor

7. Wrapping Up

4. 3. iOS: NSFetchedResultsController

1. How to Use the NSFetchedResultsController

2. Under the Hood of the NSFetchedResultsController

3. Building Our Own: ZSContextWatcher

4. Wrapping Up

5. 4. Versioning and Migration

1. Some Maintenance Before We Migrate

2. A Simple Migration

3. The Difference Between Light and Heavy Migrations

4. A Heavy/Manual Migration

5. Fundamentals of Core Data Versioning

6. Progressive Data Migration (An Academic Exercise)

7. Wrapping Up

6. 5. Performance Tuning

1. Persistent Store Types

2. Optimizing Your Data Model

3. Fetching

4. Faulting

5. Access Patterns

6. Wrapping Up

7. 6. Threading

1. Threading and Core Data

2. Working on the Main Queue

3. Working off the Main Queue

4. Interqueue Communication

5. Parent/Child NSManagedObjectContext Design

6. Export Operation

7. Import Operation

8. Asynchronous Saving

9. Debug Concurrency Checking

10. Wrapping Up

8. 7. Bulk Changes

1. Running with Scissors

2. Doing Bulk Updates

3. Notifying the Application of Changes

4. Bulk Deletes

5. Things to Consider When Using the Bulk Change APIs

6. Wrapping Up

9. 8. Using Core Data with iCloud

1. Introducing the UIManagedDocument

2. Direct NSManagedObjectContext to iCloud

3. Consuming Changes from iCloud

4. Under the Hood

5. Migrating an Existing Application

6. Desktop iCloud Integration

7. Issues with Data Quantities

8. Sharing Data Between iOS and OS X

9. Wrapping Up

10. 9. Adding a Desktop Foundation

1. Our Application

2. Our Application Design

3. Sharing the Data Model

4. Building the Controller Layer

5. Building the User Interface

6. Adding a Splash of Code

7. Wrapping Up

11. 10. OS X: Bindings, KVC, and KVO

1. Key Value Coding

2. Key Value Observing

3. Cocoa Bindings and Core Data

4. Other Interface Elements That Use KVO, KVC, and Core Data

5. Wrapping Up

12. 11. Spotlight, Quick Look, and Core Data

1. Integrating with Spotlight

2. Integrating with Quick Look

3. Putting It All Together

4. Wrapping Up

13. 12. Dynamic Parameters

1. Building the Xcode Example Project

2. The DocumentPreferences Object

3. Using Document Preferences

4. Wrapping Up

14. Bibliography

Copyright © 2016, The Pragmatic Bookshelf.

Early Praise for Core Data in Objective-C, Third Edition

Marcus’s books have long been my go-to recommendation for people who want to learn about Core Data. With new information for beginners and experts alike, they are a great insight into how you should be using Core Data and the features that it offers.

→

Daniel Tomlinson

Senior software engineer, Rocket Apps Ltd.

Core Data is a very powerful resource for any developer interested in iOS and OS X application development. This book does an excellent job of explaining the details of Core Data, a topic that is otherwise considered quite hard to grasp. A must-read book for anyone developing data-intensive applications.

→

Ramaninder Singh Jhajj

Area Services and Development Know-Center, Austria

Marcus has a profound understanding of Core Data, and this book describes not only the basics of working with Core Data, but also how to do so in a performant manner, with explanations and examples. This is an indispensable reference, alongside Apple’s documentation, and one that will quickly make Core Data your go-to persistence mechanism. You’ll be able to deal with anything that comes up with confidence.

→

Abizer Nasir

Lead developer, Jungle Candy Software Ltd.

Introduction

It is amazing to realize that as of the writing of this book, Core Data is over 12 years old. That’s an amazing number when you consider that it’s more powerful and more popular today than it has ever been. For a framework, that’s pretty spectacular.

Is This Book for You?

If you plan on writing an application that saves data to disk, you should take a very long look at Core Data. Whether you’re focusing on the desktop or the iPhone, Core Data is the most efficient solution to data persistence.

A good way to confirm that you know enough Cocoa to benefit from this book is to take a look at Chapter 1, Building a Foundation. You may find it dense, but every step should be familiar to you.

What Is Core Data?

In the simplest terms, Core Data is an object graph that can be persisted to disk. But just like describing a man as a “bag of mostly water,” that description hardly does Core Data justice. If you’ve worked with Interface Builder (specifically on OS X), you know that it effectively removes a third of the coding from the Model-View-Controller (MVC) design pattern. With Interface Builder, developers don’t need to spend countless hours writing and rewriting their user interface to make sure that it’s pixel perfect. Instead, they simply drag and drop the elements in the IDE, bind them together, and call it done.

Of course, the problem with Interface Builder is that we still need to code the other two parts! Both the controller and the model need to be developed in code and made to work with the interface we just designed. That’s where Core Data comes in. In a nutshell, Core Data deals with a third of that MVC design: Core Data is the model.

It’s a common misconception that Core Data is a database API for Cocoa that allows a Cocoa application to store its data in a database. Although that description is factually accurate, Core Data does a lot more. It serves as the entire model layer. It’s not just the persistence on disk; it’s also all the objects in memory that we normally consider to be data objects. If you have experience working with Java, C#, or some other object-oriented language, you know that the data objects take a lot of time to write and that they’re generally very repetitive in nature. Core Data eliminates most, if not all, of that boilerplate code for us and lets us focus on the business logic, or the controller layer, of our application. It does this with an interface that’s as easy to use as Interface Builder.

In addition to ease of use, Core Data is highly flexible. If we need to step in and change the functionality of some portion of the data model, we can. From how a value is handled when it’s being accessed to how data is migrated from one persistent store to another, we can choose how little or how much we want to code ourselves and how much we want Core Data to do for us.

When you start to learn Core Data, it’s best to think in terms of objects. Core Data is a framework designed to manage your data and data object graph. As a secondary function, it will persist that data to disk. However, its primary function is to manage the objects.

Online Resources

The app and examples shown in this book can be found at the Pragmatic Programmers website for this book.[1] You’ll also find the community forum and the errata-submission form, where you can report problems with the text or make suggestions for future versions.

Footnotes

[1]

http://pragprog.com/book/mzcd3/core-data-in-objective-c-third-edition

Copyright © 2016, The Pragmatic Bookshelf.

Chapter 1

Building a Foundation

There’s an interesting challenge when you’re writing a book for what is effectively a subsystem. How does one describe the subsystem without being distracted by the overall system that it intended to interact with? This is the problem that we’re solving in this first chapter of the book. In this chapter we aren’t going to talk about Core Data; instead, we’re going to, very quickly, build up an app for our Core Data work to interact with. We can then use this application throughout most of the rest of the book to run and play with while learning about Core Data.

On our journey to learn about Core Data, we’re going to use an original iPhone recipe application. While the application itself is fairly complex and contains a large number of views, the concepts behind these views are standard and will be familiar to anyone who has done work in Objective-C. If you haven’t yet mastered Objective-C and Cocoa, I highly suggest you review Chris Adamson and Janie Clayton’s iOS 9 SDK Development [Cla16], published by the Pragmatic Bookshelf.

Our goal in this chapter is to establish a baseline application from which to work. Core Data is a supporting framework, and we need something for it to support in order to explore and demonstrate its features and characteristics. This recipe application is the foundation used in the book. You can use this chapter as a reference of the overall picture of the application as we’re exploring the inner workings of Core Data in the rest of the chapters.

If you’re already comfortable with storyboards and the creation of iOS applications, feel free to skim the provided code sample and jump to Chapter 2, Under the Hood of Core Data, in which we’ll begin to dive into the substance of Core Data.

The Storyboard

To keep the structure of our application as simple as possible, we’ll use a storyboard to design the entire UI of our iPhone application. As you’ll recall from your reading or experience, a storyboard allows us to view the entire application interaction in one place. We can also control a greater portion of the UI within the storyboard and thus allow for even less code to be introduced to our project. You can see a zoomed-out version of our storyboard in the following screenshot.

This storyboard seems complex—and it is. However, most of the code needed to support these views is minimal, and there’s even a fair amount of view controller reuse that makes the application easier to develop and maintain.

There are three primary elements to this application: a list of recipes, a detailed view of a single recipe, and the ability to add/edit recipes. The add/edit feature is the most complex, so we’ll save it for last.

The Recipe List

When users launch our application, they’re immediately presented with a list of recipes. The list is represented in the storyboard as a UITableView with a prototype cell. We’re using a basic cell in the prototype, as shown here.

Associated with this UI element in the storyboard, we also have a UIViewController named PPRMasterViewController. The data for this UITableViewController is driven by an NSFetchedResultsController (which we explore in detail in Chapter 3, iOS: NSFetchedResultsController). Right now we’re concerned with the segues of this UIViewController. The segues describe the transition that our application uses when going from one view controller to another. A segue can happen when we select a row in a table, a button in the navigation bar, a button on the tab bar, or virtually anything that causes the UI to move to another view controller.

RecipesV1/PPRecipes/PPRMasterViewController.m

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

{

if ([[segue identifier] isEqualToString:@"showRecipe"]) {

[self prepareForDetailSegue:segue sender:sender];

return;

} else if ([[segue identifier] isEqualToString:@"addRecipe"]) {

[self prepareForAddRecipeSegue:segue sender:sender];

return;

}

ALog(@"Unknown segue: %@", [segue identifier]);

}

When a segue is activated, the current UIViewController receives a call to ‑prepareForSegue: sender:. That’s our opportunity to prepare the next UIViewController for display. It’s a good idea to use this method as a branching point for each potential segue. Taking this step aids with code clarity and maintenance. With one or two segues, this method is fairly easy to maintain. However, when we have many possible segues, the code quickly becomes unwieldy. Instead of waiting for the code to become unwieldy and refactoring it, we start off by using this method as a branching point.

RecipesV1/PPRecipes/PPRMasterViewController.m

- (void)prepareForDetailSegue:(UIStoryboardSegue*)segue sender:(id)sender

{

NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];

id object = [[self fetchedResultsController] objectAtIndexPath:indexPath];

[[segue destinationViewController] setRecipeMO:object];

}

The first segue brings in the PPRDetailViewController and displays a single recipe in detail. The method ‑prepareForDetailSegue: sender: demonstrates the dependency injection pattern. We obtain a reference to the about-to-be-exposed UIViewController, and we inject the information that it needs. We’ll use this pattern extensively throughout the book.

RecipesV1/PPRecipes/PPRMasterViewController.m

- (void)prepareForAddRecipeSegue:(UIStoryboardSegue*)segue sender:(id)sender

{

NSManagedObjectContext *context = nil;

NSEntityDescription *entity = nil;

NSManagedObject *newMO = nil;

context = [[self fetchedResultsController] managedObjectContext];

entity = [[[self fetchedResultsController] fetchRequest] entity];

newMO = [NSEntityDescription insertNewObjectForEntityForName:[entity name]

inManagedObjectContext:context];

[[segue destinationViewController] setRecipeMO:newMO];

}

The second segue branches us into the editing capabilities of our application. This is our first bit of code reuse.

Instead of having an “add” logic path and an “edit” logic path, the paths are combined. In fact, their functionality is 99 percent identical. The 1 percent difference between them concerns whether an object is being created or an existing object is being edited. By again using dependency injection, we pull that 1 percent difference out of the logic path and let the parent UIViewController make the decision. As far as the rest of our editing workflow is concerned, there’s no difference. It’s being handed a data object, and it doesn’t matter whether the object has been created anew or whether it previously existed.

The Recipe Detail

When users select a recipe in our application, we want to display everything about the recipe in one screen so they can easily absorb the information and prepare the recipe. We’ll need one fairly complex UIViewController in order to give them that one-screen access. Take a look at the following screenshot for a sample of the view.

In our UIViewController, we’ll take the data object that was passed to us and display it in one (potentially) lengthy UIScrollView.

The edit button in the UINavigationBar is the interesting part of this view controller. When the edit button is clicked, we enter the edit workflow (we’ll discuss this further in the next section). This process is identical to the add workflow we discussed in The Recipe List. With this code reuse, we can now enter the same workflow from the view controller that allows us to view a recipe as we did from the list recipes view controller. Both of these produce the same effect: we can edit a recipe, and it doesn’t matter if that recipe is new or existing.

RecipesV1/PPRecipes/PPRDetailViewController.m

- (void)prepareForEditSegue:(UIStoryboardSegue *)segue sender:(id)sender

{

id controller = [segue destinationViewController];

[controller setRecipeMO:[self recipeMO]];

}

Note the subtle difference in this reuse. In the previous version of the edit workflow, we created a new data object. In this version, we take our existing reference to the data object and hand it off to the edit workflow.

The Edit Workflow

The complexity of our application really lives in the edit workflow shown here. It’s in this workflow that we edit, delete, and change data objects.

The bulk of the complexity of the edit workflow is in the very first UIViewController. To begin with, this UITable ViewController uses static cells, as opposed to prototype cells. In this design, we have a known quantity of cells, and each cell displays a different piece of information.

By utilizing static cells, we do a significant portion of the work of the edit workflow directly in the storyboard. We avoid handling a large amount of complexity in our application when a user selects a cell. In other words, instead of having to figure out which cell was selected, determine the view controller to build, populate that view controller, present it, and so on, we can have the storyboard do the bulk of that work for us. Each cell in this UITableViewController has a segue to another UIViewController. Each of those segues is named, and we can therefore do away with a large portion of code. Our UITableViewController subclass needs to look only at the identifier of the segue and inject the right dependencies.

RecipesV1/PPRecipes/PPREditRecipeViewController.m

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

{

NSString *identifier = [segue identifier];

if ([identifier isEqualToString:@"editRecipeName"]) {

[self prepareForEditRecipeNameSegue:segue sender:sender];

} else if ([identifier isEqualToString:@"selectRecipeType"]) {

[self prepareForSelectTypeSegue:segue sender:sender];

} else if ([identifier isEqualToString:@"selectNumberOfServings"]) {

[self prepareForSetServingsSegue:segue sender:sender];

} else if ([identifier isEqualToString:@"selectIngredients"]) {

[self prepareForSelectIngredientsSegue:segue sender:sender];

} else if ([identifier isEqualToString:@"editDescription"]) {

[self prepareForDirectionsSegue:segue sender:sender];

} else {

ALog(@"Unknown segue: %@", identifier);

}

}

Here we see the value of using the ‑prepareForSegue: sender: method only for branching. Had we put all of the flow logic into this one method, it would easily be 100 lines of code or more and be a mess to maintain.

All of the UIViewController instances that are accessed from the edit UITableViewController fall into one of two categories: edit something or select something. Let’s look at an example of both kinds of instance. See the screenshot.

Text Edit View Controller

The PPRTextEditViewController is easily the most reused UIViewController. The bulk of a recipe is text, and the text is probably going to need to be edited. As a result, the process of editing in our application must be highly reusable. This is also a great opportunity to use a block callback to assist in the reusability of the PPRTextEditViewController.

RecipesV1/PPRecipes/PPREditRecipeViewController.m

- (void)prepareForEditRecipeNameSegue:(UIStoryboardSegue *)segue

sender:(id)sender

{

id editRecipeNameVC = [segue destinationViewController];

NSString *name = [[self recipeMO] valueForKey:@"name"];

[[editRecipeNameVC textField] setText:name];

[editRecipeNameVC setTextChangedBlock:^ BOOL (NSString *text,

NSError **error) {

NSIndexPath *path = [NSIndexPath indexPathForRow:0 inSection:0];

UITableViewCell *cell = [[self tableView] cellForRowAtIndexPath:path];

[[cell detailTextLabel] setText:text];

[[self recipeMO] setValue:text forKey:@"name"];

return YES;

}];

}

The most interesting part of this segue preparation is the callback. The PPRTextEditViewController is actually quite dumb. Its entire job is to consume the text entered into a text field and listen for either the Done button to be clicked or the Return key to be tapped. When either of those things happens, it takes the text from the UITextField (or the UITextView) and passes it to the callback block.

Note that the block accepts both an NSString and an NSError pointer. The parent, which defines the block, then validates the text and sets the error if there’s a problem. The PPRTextEditViewController receives a pass/fail from the block. In the event of a failure, it displays the error.

If the block returns a pass, the text view controller pops back to its parent.

With this design, we can use the PPRTextEditViewController to edit any piece of text we choose, and it doesn’t need any custom or special handling code. The only thing we need to do for each instance is set up its view in the storyboard and pass in the text that it needs to start with. By customizing the view, we also gain access to which keyboard is appropriate for each text edit, thereby making it easy to reuse this UIViewController for numeric text, email addresses, or virtually any data we can think of.

The second function of the callback block is to update the data object and the edit view. Since we’re using static cells, we must grab a reference to the existing cell and refresh it based on the text being passed into the block.

List Ingredients View Controller

While there are a couple of “select something” items in the PPREditRecipeViewController, the ingredients selection is by far the most interesting. First, we want to display all of the existing ingredients. To do this, we need a selection view controller. Next, we want to add ingredients, which means we must add an Add Ingredient row when the user taps the Edit button. When the users select the Add Ingredient row, we then have to put them into another view controller that allows them to edit the individual components of an ingredient. In the Add Ingredient view, we then need to allow the user to set the quantity, type of ingredient, and unit of measure. The end result can be seen here:

Later, as we update and enhance our application, this workflow becomes even longer and more complicated. Fortunately, the code stays quite sane with the reuse of view controllers and the use of the storyboard.

Unfortunately, we need to build two specific view controllers for this workflow: one to handle listing the ingredients (including adding and deleting) and one for adding an ingredient. Fortunately, we can get a lot of reuse from our text edit view controller in this part of the application.

Ready for Core Data

I’ve intentionally glossed over the Core Data portions of the application in this chapter. This is the foundation we’ll use as you learn and work with Core Data. In Chapter 2, Under the Hood of Core Data, we step through the components of the Core Data structure of this application and discuss each in detail. At the end of that chapter, you’ll have a firm understanding of the Core Data stack and how to access it. From there we’ll begin to utilize some of the more advanced concepts behind Core Data to improve upon our application.

Copyright © 2016, The Pragmatic Bookshelf.

Chapter 2

Under the Hood of Core Data

Part of the barrier to entering the world of Core Data is that it is perceived as having a steep learning curve. Looking at the code samples that Apple provides for developers, you might at first glance agree with that evaluation. That perception was born from the first few iterations of the framework. When Core Data was first introduced there was a large amount of confusion over what needed to be done, what didn’t, and what we were allowed to do with the framework. A lot has changed since those early days. As you’ll see in the next couple of chapters, Core Data is extremely easy to use and when you need the complexity to handle an edge case, the options are available.

In my experience of working with and writing persistence layers for various languages, I’m constantly amazed at how simple and elegant the Core Data API is. There’s very little overlap in functionality between the individual pieces of Core Data—no wasted space or unnecessary redundancy. Because Core Data is built on the infrastructure of Objective-C and Core Foundation, it doesn’t seek to duplicate functionality that already exists in other parts of the overall API but instead uses that functionality to its full extent.

In this chapter, we’ll go through the key pieces of Core Data and remove some of the mysticism that surrounds them. By the end of this chapter, you’ll have a much higher comfort level and you’ll be able to understand what all of that sample code does. We’ll also be discussing the terminology of Core Data so that you can better understand what the individual components are and how they work together.

At its most basic level, Core Data is an object graph designed to manage data objects. It’s common when you’re first approaching Core Data to think of it as an object-relational mapping framework—and in truth, it can be used as such. However, that’s not the best approach. My elevator pitch for Core Data goes like this: “Core Data is an object graph that can persist to a database.” The primary function of Core Data is to manage the object graph. Persisting the data to disk is a secondary, although vital, function.

The Core Data API, or stack as it is commonly called, consists of three primary pieces: the NSManagedObjectModel, the NSPersistentStoreCoordinator, and the NSManagedObjectContext, as shown here.

All of these pieces work together to allow a program to retrieve and store NSManagedObject instances. In most situations, the program will access the NSManagedObjectContext only once the stack has been created. It’s possible to access the other components of the stack, but it’s rarely necessary. Each of the three pieces of the Core Data stack work together. As we’ll review in detail, the NSManagedObjectModel feeds structure to the NSPersistentStoreCoordinator and the NSPersistentStoreCoordinator feeds data and structure to the NSManagedObjectContext. The NSManagedObjectContext in turn handles requests from the rest of the application to create, update, delete, and insert NSManagedObject instances.

Throughout this chapter, and the rest of the book, we’ll be referencing the application that we built in Chapter 1, Building a Foundation. We’ll start that reference with exploring the creation of the Core Data stack.

Introducing the NSManagedObjectModel

The first part of our exploration of the components of Core Data is the portion of the framework that’s the least accessed: the NSManagedObjectModel. An object model is a way to describe the data in terms of objects. The NSManagedObjectModel is a compiled, binary version of the data model that we create graphically in Xcode. When we say that we’re manipulating the object model, we mean we’re editing the source file in Xcode that will get compiled and used by the NSManagedObjectModel. From a database perspective, this file represents the schema of the database. In Xcode, this file is shown in two different styles; the easier of the two to recognize is shown here.

While this view is great for conceptualizing the data and the objects, it’s not great for editing. Therefore, the style can be changed using a control in the lower-right corner called Editor Style. The second style is shown here.

The NSManagedObjectModel—and, by extension, the associated source file in the object model—is what we think of when we need to change the object model. The source file is saved as an xcdatamodel file. That file is stored within a Cocoa style bundle that starts out with the same filename. If you were to look at the file structure directly you would see a folder with the xcdatamodeld extension and then you would find the actual data model file inside. The reason for this structure is explained in Chapter 4, Versioning and Migration.

Xcode understands how to compile these files in this bundle. When Xcode encounters these files during the compile process of a project, it compiles them into a binary form ending with the extension mom inside of a bundle with the momd extension. We can also build these files from the command line using the momc tool.

Editing the Data Model

In Chapter 1, Building a Foundation, the sample project started with the data model already created. Let’s look at how that that data model was created. To begin with, we told Xcode to create a new file (File > New > File...), and we selected Data Model from the template, as shown here.

This presents us with a blank data model ready to be edited. From here, in grid view, we added the three entities being used in the project: Recipe, RecipeIngredient, and Type. We then added the attributes and relationships for each of those entities. For a discussion of entities, attributes, and relationships, take a look at Working with NSManagedObject Instances.

Loading the Data Model

Once we’ve created the source file for the data model, we need to instantiate it within our application. In the Xcode sample projects, this is generally done in its own method in the application’s AppDelegate. However, this process is unnecessary and tends to add to code bloat in the AppDelegate. My preference is to treat Core Data, really the entire persistence layer, as a first-class citizen in the application. What this means is that we can create a new controller that’s a subclass of NSObject and place all of the Core Data--related code into that controller. From there we can instantiate that controller within the Application Delegate.

Further, I like to construct the entire Core Data stack in a single method. There’s rarely a situation where you wish to build only part of the stack. With that structure, you can kick off the initialization of the Core Data stack as soon as the application launches so that it’s available, in some form, immediately. Therefore, in our sample application, we have a method named ‑initializeCoreDataStack that starts off with constructing the data model.

RecipesV1/PPRecipes/PPRDataController.m

- (void)initializeCoreData;

{

if ([self managedObjectContext]) return;

NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"PPRecipes"

withExtension:@"momd"];

NSAssert(modelURL != nil, @"Failed to locate momd in app bundle");

NSManagedObjectModel *mom = nil;

mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

To initialize the NSManagedObjectModel, we first need to locate it within the application bundle. We call upon the NSBundle and retrieve the ‑mainBundle, which represents the application bundle. From there, we call ‑URLForResource: withExtension: using the name of our data model—in this case PPRecipes—and the extension momd. We use an assertion and verify that we did receive an NSURL. We then initialize the NSManagedObjectModel with that NSURL. We again verify that everything worked correctly by checking the new instance against nil.

And that’s everything involved in constructing the NSManagedObjectModel. Our next step is to construct the NSPersistentStoreCoordinator, which uses the NSManagedObjectModel we just initialized.

Integrating with the NSPersistentStoreCoordinator

The NSPersistentStoreCoordinator is the true maestro of Core Data. The NSPersistentStoreCoordinator is responsible for persisting, loading, and caching data. Think of the NSPersistentStoreCoordinator as the heart of Core Data. Having said this, we do very little work with the NSPersistentStoreCoordinator directly. We work with it upon initialization, but we almost never touch it again over the life of the application.

As part of our initialization, we perform two steps with the NSPersistentStoreCoordinator. First, we initialize it, which requires a valid NSManagedObjectModel. Once it’s initialized, we add one or more NSPersistentStore instances. An NSPersistentStore is a representation of a location in which the data is saved/persisted. Typically, this persistence is done to disk. However, that step isn’t required; it could be in memory or even over the network. For now, let’s focus on disk persistence. The NSPersistentStore is responsible for describing the file format used. This file format can be one of several: SQLite, binary, or atomic. (There’s also an XML store for OS X, but I don’t recommend using it because it’s not available on iOS, nor does it perform very well.) To keep our focus, we’ll use the SQLite format in this first iteration of our application and explore the other formats later in the book.

In previous versions of Core Data and the sample projects, the initialization of the NSPersistentStoreCoordinator and the addition of the NSPersistentStore were done in a single method. This example tended to lead to a number of issues for developers because they didn’t fully understand the impact of the example. Therefore, we’re going to do this initialization in a more complex way, but it will be a way that won’t paint us into a corner.

RecipesV1/PPRecipes/PPRDataController.m

NSPersistentStoreCoordinator *psc = nil;

psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];

In this first bit of code, we initialize the NSPersistentStoreCoordinator and pass it the NSManagedObjectModel that we previously initialized. This call returns immediately, and therefore we can do it inline as part of the start-up of the application.

Adding one or more NSPersistentStore instances to the NSPersistentStoreCoordinator, however, can take an unknown amount of time. The reason for the unpredictability is that we could be performing a migration during the call (as discussed in Chapter 4, Versioning and Migration), or we could be linking and updating with iCloud (as discussed in Chapter 8, Using Core Data with iCloud). If either of those situations occurs, the addition of the NSPersistentStore can delay the launch of the application to the point of providing a poor user experience or, worse, being terminated by the operating system. To avoid either of these situations, we want to add the NSPersistentStore on a background queue so that the application can finish launching while we perform our start-up.

RecipesV1/PPRecipes/PPRDataController.m

dispatch_queue_t queue;

queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

dispatch_async(queue, ^{

NSFileManager *fileManager = [NSFileManager defaultManager];

NSArray *urls = [fileManager URLsForDirectory:NSDocumentDirectory

inDomains:NSUserDomainMask];

NSURL *documentsURL = [urls lastObject];

NSURL *storeURL = nil;

storeURL = [documentsURL URLByAppendingPathComponent:@"DataModel.sqlite"];

NSError *error = nil;

NSPersistentStore *store = nil;

store = [psc addPersistentStoreWithType:NSSQLiteStoreType

configuration:nil

URL:storeURL

options:nil

error:&error];

if (!store) {

NSLog(@"Error initializing PSC: %@\n%@", [error localizedDescription],

[error userInfo]);

}

In this portion of the code, we grab a reference to a global queue with a default priority. Then we add a block to be executed on that queue that will handle the addition of the NSPersistentStore to the NSPersistentStoreCoordinator.

Inside that block, we first determine where we want to store the file associated with our NSPersistentStore. In this example, we’re going to put it into the Documents directory that’s part of the application sandbox. If we were working on OS X, we could put it in the Application Support folder or anywhere else that was appropriate. We resolve this path using the NSFileManager and call its ‑URLsForDirectory: inDomains: method, which will return an array of NSURL instances. We call ‑lastObject on that array to retrieve the last NSURL. We then append the filename for our NSPersistentStore to the end of that NSURL.

With the location of the store now resolved, we can add the NSPersistentStore to the NSPersistentStoreCoordinator. We do this with a call to ‑addPersistentStoreWithType: configuration: URL: options: error:. This is a complex method call, so let’s break it down by parameter. There are five in all.

- The first parameter is Type. This tells the NSPersistentStoreCoordinator what type of store we want initialized. In this case, we’re passing NSSQLiteStoreType to indicate we want a SQLite store. This is the parameter to change if we want to use another store type.

- The second parameter is configuration. This is an advanced setting that allows us to partition our data model into different configurations for different uses. Since we aren’t partitioning our data model at this time, we’ll pass nil, which tells the NSPersistentStoreCoordinator that we want to use the default configuration.

- The third parameter, URL, accepts the NSURL for the store. We pass in the NSURL that we resolved earlier.

- The fourth parameter, options, allows us to change the behavior of the NSPersistentStore. This parameter is used during versioning, during iCloud configuration, and for on-disk encryption. We aren’t using any of these features at this time, so we pass nil here as well.

- The last parameter, error, is used when something goes wrong with the addition of the NSPersistentStore. It’s tempting to pass nil here as well, but I strongly advise against it. This is the only indicator when something goes wrong. I recommend passing a pointer to an NSError variable so that we can interrogate the error if something goes wrong.

This call will either return an NSPersistentStore or return nil. If it returns nil, that means something failed, and we need to interrogate the error. Because we don’t normally need to have a reference to the NSPersistentStore after its creation, let’s just check the return for nil and continue.

Once this call is completed, our NSPersistentStoreCoordinator is fully initialized and ready to be used. Since we’ve completed this step on a background queue, it’s helpful to notify the UI that it’s ready to be used. Therefore, we end the block with a call back onto the main queue and allow the UI to complete its initialization. This completion could include removing a modal dialog or even just telling the view controllers to reload themselves. The exact experience is left up to the developer.

RecipesV1/PPRecipes/PPRDataController.m

if (![self initCallback]) return;

dispatch_sync(dispatch_get_main_queue(), ^{

[self initCallback]();

});

});

Once we initialize the NSPersistentStoreCoordinator, we rarely, if ever, access it directly again. It silently works in the background, persisting the data. Because of this, we don’t need to keep a reference to it directly; instead, we can rely on the NSManagedObjectContext to do that for us.

Adding the NSManagedObjectContext

Next to NSManagedObject, NSManagedObjectContext is the object in the Core Data stack that we’ll most often access. The NSManagedObjectContext is the object we access when we want to save to disk, when we want to read data into memory, and when we want to create new objects. The NSManagedObjectContext is at the top of the Core Data stack in that it’s accessed directly by our code frequently. It’s much less common for us to need to go deeper into the stack.

NSManagedObjectContext cannot be accessed from multiple threads. Each thread that needs access to data should have its own NSManagedObjectContext. This is generally not an issue, because it’s very easy to create multiple contexts as needed for data that has to be addressed in the background. Using Core Data in a multithreaded environment is discussed in detail in Chapter 6, Threading.

RecipesV1/PPRecipes/PPRDataController.m

NSManagedObjectContext *moc = nil;

moc = [NSManagedObjectContext alloc];

moc = [moc initWithConcurrencyType:NSMainQueueConcurrencyType];

[self setManagedObjectContext:moc];

[moc setPersistentStoreCoordinator:psc];

The NSManagedObjectContext itself is fairly straightforward to initialize. The initialization of the NSManagedObjectContext requires a parameter to describe whether or not it will be used by the user interface. In our first iteration of the Core Data stack we’re creating a single NSManagedObjectContext that will be used by the user interface. Therefore, we pass the parameter NSMainQueueConcurrencyType as part of the initialization of the context so that it will be configured to be used on the main queue. The different types of contexts are discussed in detail in Chapter 6, Threading.

Once the NSManagedObjectContext has been initialized, we only need to set the NSPersistentStoreCoordinator that it’s going to access. From there, it’s ready to be used. If you look at our sample application, you’ll notice that the order in which these events were described is different from the order they appear in the code. In the code, the NSManagedObjectContext is actually initialized prior to kicking off the block to add the NSPersistentStore to the NSPersistentStoreCoordinator. The reason for this is one of potential order. We want to guarantee that the @property for the NSManagedObjectContext is set before the NSPersistentStore is added and before the ‑contextInitialized method is called. Although it’s highly unlikely that the block on the queue would complete before the method we’re in, there’s no reason to risk it.

Working with NSManagedObject Instances

The NSManagedObject is the object we work with the most in a Core Data application. Each instance of the NSManagedObject represents one entity in our Core Data repository. By combining Core Data with KVO (Key Value Observing) and KVC (Key Value Coding), this one object can dynamically represent any object that we need and that can be defined in our data model. KVO and KVC are discussed in great detail in Chapter 10, OS X: Bindings, KVC, and KVO. To learn more about them, I highly recommend you read Apple’s excellent documentation on the subjects.[2]

All of the properties and relationships defined in our data model are available and easy to access directly from the NSManagedObject. Without subclassing it, we can access values associated with an NSManagedObject in the following ways.

Accessing Attributes

Attributes are the easiest to access. By utilizing KVC, we can get or set any attribute on the NSManagedObject directly. If you’ve reviewed our sample app in Chapter 1, Building a Foundation, you may have noticed that we didn’t write a Recipe class. At this point in our application, the NSManagedObject provides all of the required functionality. We could get the name as follows:

NSManagedObject *recipe = ...;

NSString *name = [recipe valueForKey:@"name"];

Likewise, we can set the name in a similar fashion, as follows:

NSManagedObject *recipe = ...;

[recipe setValue:@"New Name" forKey:@"name"];

When we want to subclass NSManagedObject, we can also define properties for the attributes (and relationships discussed in a moment) so that we can access them directly. In the header of the subclass, define the properties normally.

RecipesV2/PPRecipes/PPRRecipeMO.h

@interface PPRRecipeMO : NSManagedObject

@property (strong) NSString *desc;

@property (strong) NSString *imagePath;

@property (strong) NSString *lastUsed;

@property (strong) NSString *name;

@property (strong) NSString *serves;

@property (strong) NSString *type;

As you can see, we’re defining the property like normal, but there are no ivars (instance variables) associated with those properties. Since these properties are created dynamically at runtime, we don’t need to declare them in the header. However, we do need to flag them as dynamic so that the compiler won’t issue a warning. This is done in the implementation file.

RecipesV2/PPRecipes/PPRRecipeMO.m

#import "PPRRecipeMO.h"

@implementation PPRRecipeMO

@dynamic desc;

@dynamic imagePath;

@dynamic lastUsed;

@dynamic name;

@dynamic serves;

@dynamic type;

By declaring them as @dynamic, we’re telling the compiler to ignore any warnings associated with these properties because we promise to generate them at runtime. Naturally, if they turn up missing at runtime, our application is going to crash. However, when we’re working with NSManagedObject objects, the attributes will be looked up for us, and we don’t need to implement anything. By adding that code, we can access the attribute directly, as shown in the following example:

PPRecipe *myRecipe = ...;

NSString *recipeName = [myRecipe name];

//Do something with the name

[myRecipe setName:recipeName];

No warnings or errors will be generated with this code, and when our application runs we’ll be able to access the properties just like any “normal” subclass of NSObject.

Understanding Primitive Access

It should be noted that accessing the attribute via KVC or properties will trigger KVO notifications that the attribute has changed. There are situations where we don’t want this to occur or where we prefer it to occur later. In those situations, we can access the attribute using the ‑primitiveValueForKey: and ‑setPrimitiveValue:forKey: methods. Both of these methods work the same as the ‑valueForKey: and ‑setValue:forKey methods that we’re used to working with, but they don’t cause KVO notifications to fire. This means the rest of our application will be unaware of any changes we make until and unless we notify it.

This is quite useful when you’re loading in data from an external source and the data is going to impact several attributes at once. Imagine we wrote a recipe importer that accepted a comma-separated value (CSV) file from another recipe application. In that situation, we might not want the UI or other parts of our application making decisions based on the data in the middle of the import. Therefore, we’d want to update the values without notifications, and once all of them were done, we’d let the notifications go out. The code to handle this situation would look like this:

- (void)importData:(NSDictionary*)values //CSV translated into a dictionary

{

[self willChangeValueForKey:@"name"];

[self willChangeValueForKey:@"desc"];

[self willChangeValueForKey:@"serves"];

[self willChangeValueForKey:@"type"];

[self setPrimitiveValue:[values valueForKey:@"name"] forKey:@"name"];

[self setPrimitiveValue:[values valueForKey:@"desc"] forKey:@"desc"];

[self setPrimitiveValue:[values valueForKey:@"serves"] forKey:@"serves"];

[self setPrimitiveValue:[values valueForKey:@"type"] forKey:@"type"];

[self didChangeValueForKey:@"type"];

[self didChangeValueForKey:@"serves"];

[self didChangeValueForKey:@"desc"];

[self didChangeValueForKey:@"name"];

}

In this example code, we’re handling all the change notifications ourselves and setting the values into our NSManagedObject directly using the ‑setPrimitiveValue:forKey: method. This will cause all the values to be updated prior to the notifications being fired.

Accessing Relationships

Accessing relationships on an NSManagedObject is nearly as easy as accessing attributes. There’s a bit of a difference between working with a to-one relationship and a to-many relationship, though.

Accessing a To-One Relationship

When we’re accessing a to-one relationship, we can treat it the same as we would an attribute. Since Core Data is first and foremost an object graph, a to-one relationship can be treated exactly like a property that contains any other object. For example, the relationship between RecipeIngredient and its Recipe is a to-one relationship from the RecipeIngredient side. Therefore, if we were accessing this relationship from that point of view, the code would be as follows:

NSManagedObject *ingredient = ...;

NSManagedObject *recipe = [ingredient valueForKey:@"recipe"];

In this example, we’re using the ‑valueForKey: KVC method to access the relationship, and the NSManagedObject returns the object on the other side of the relationship, which is the entity. Likewise, to set the recipe for an ingredient, we’d use the following code:

NSManagedObject *ingredient = ...;

NSManagedObject *recipe = ...;

[ingredient setValue:recipe forKey:@"recipe"];

This will set the object on the other side of the relationship.

Accessing a To-Many Relationship

The many side of a relationship is stored unordered. This means each time we fetch the objects on the many side of a relationship, the order isn’t guaranteed, and it’s probable that the order will change between fetches. However, we’re guaranteed that each object will be included only once. In other words, when we access a to-many relationship using KVC, we’ll get an NSSet back. For example, if we want to access the ingredients of a recipe, we’d use code similar to the following:

NSManagedObject *recipe = ...;

NSSet *ingredients = [recipe valueForKey:@"ingredients"];

Likewise, setting the ingredients into a recipe is as follows:

NSManagedObject *recipe = ...;

NSSet *someIngredients = ...;

[recipe setValue:someIngredients forKey:@"ingredients"];

This will set which ingredients are associated with the recipe.

Mutable Access of To-Many Relationships

You might notice that the NSSet we get back when accessing a to-many relationship is immutable. Adding an object to a to-many relationship with an immutable NSSet requires creating a mutable copy of the NSSet, adding the new object to the NSMutableSet, and setting the NSMutableSet back onto the parent object. It’s a painful process and, fortunately, unnecessary. When we want to add an object to a to-many relationship, we can use ‑mutableSetValueForKey: in the place of ‑valueForKey:. This returns an NSMutableSet for us that’s already associated with the parent object and reduces our code to the following:

NSManagedObject *newIngredient = ...;

NSManagedObject *recipe = ...;

NSMutableSet *ingredients = [recipe mutableSetValueForKey:@"ingredients"];

[ingredients addObject:newIngredient];

Note that we didn’t need to set the NSMutableSet back into the entity, and therefore the code to add an object to a to-many relationship is quite short.

One important thing to notice in these relationship examples is that when we update the relationship, we’re updating only one side of it. Because we defined these relationships as double-sided (that is, they include an inverse relationship that we defined in Introducing the NSManagedObjectModel), Core Data handles keeping the integrity of the relationships intact. When we update one side of a relationship, Core Data automatically goes in and sets the other side for us.

Primitive Access

Similar to the process of accessing attributes discussed earlier, changes to a relationship will fire KVO notifications. Since there are situations in which we don’t want this to occur or in which we want a finer-grained control over the notifications, there are primitive accessors for relationships as well. However, there is no primitive method for retrieving an NSMutableSet for a to-many relationship. Therefore, if the code requires changes to a relationship with either delayed or no observations being fired, we must use ‑primitiveValueForKey: to get back an NSSet, call ‑mutableCopy on the NSSet, add our new object to the NSMutableSet, and finally use ‑setPrimitiveValue:forKey: to apply the changes.

Property Accessors

Relationships can use properties, just like the attributes discussed earlier. In the code in Mutable Access of To-Many Relationships, if we want to add a property to retrieve the relationship, we declare the following property:

@property (strong) NSSet *recipeIngredients;

And then we flag it as dynamic in the implementation file.

Subclassing NSManagedObject

Although NSManagedObject provides a tremendous amount of flexibility and handles the majority of the work a data object normally does, it doesn’t cover every possibility, and there are occasions where we might want to subclass it. Subclassing to gain @property access to attributes and relationships is one common situation, but we may also want to add other convenience methods or additional functionality to the object. When such a situation arises, you must keep in mind a few general rules.

Methods That Are Not Safe to Override

In Apple’s documentation, the methods in the table that follows should never be overridden.

Table 1. Methods never to override

‑primitiveValueForKey:

‑setPrimitiveValue:forKey:

‑isEqual:

‑hash

‑superclass

‑class

‑self

‑zone

‑isProxy:

‑isKindOfClass:

‑isMemberOfClass:

‑conformsToProtocol:

‑respondsToSelector:

‑retain

‑release

‑autorelease

‑retainCount

‑managedObjectContext

‑entity

‑objectID

‑isInserted

‑isUpdated

‑isDeleted

‑isFault

‑alloc

‑allocWithZone:

+new

+instancesRespondToSelector:

+instanceMethodForSelector:

‑methodForSelector:

‑methodSignatureForSelector:

‑isSubclassOfClass:

It’s quite a list. Most, if not all, of these methods are common sense, and experience with Objective-C explains why they shouldn’t be overridden. Even though this is a fairly long list, I’m going to add a few more.

-initXXX

The first is ‑initXXX. There’s really no reason or benefit to overriding the ‑init methods of an NSManagedObject, and there are situations in which doing so has unpredictable results. Although it’s not specifically against the documentation to override the ‑init methods, I recommend strongly against it. The ‑awakeFromInsert and ‑awakeFromFetch methods provide sufficient access that overriding ‑init is unnecessary. (Both ‑awakeFromInsert and ‑awakeFromFetch are discussed in more depth later in this chapter.)

KVO Methods

I’d also add all of the KVO methods. The documentation flags these methods as “discouraged,” but I’d put them right in the “do not subclass” list. There’s no reason to override these methods, and any logic that you’d want to put into them can probably be put somewhere else with fewer issues.

-description

In addition, there’s the ‑description method, used fairly often in logging. It’s a great way to dump the contents of an object out to the logs during debugging. However, when we’re dealing with faults (discussed in Chapter 5, Performance Tuning), we don’t want to fire a fault in the ‑description method. Since the default implementation of ‑description does the right thing with regard to faults, it’s best that we avoid overriding its behavior.

Joe asks:

What Is a Fault?

Core Data faults are similar to virtual memory page faults. Faulted objects are scoped objects that may or may not actually be in memory, or “realized,” until you actually use them. Although there’s no guarantee for when a faulted NSManagedObject will be loaded into memory, it’s guaranteed to be loaded when accessed. However, the object will be an instance of the appropriate class (either an NSManagedObject or the designated subclass), but its attributes aren’t initialized.

Methods to Override

With the long list of methods that we shouldn’t override, what methods should we consider overriding? There are a few methods we’ll commonly override.

-didTurnIntoFault

This method is called after the NSManagedObject has been turned into a fault. It’s a good place to release transient resources. One important thing to note is that when this method is called, all the stored values/relationships in the NSManagedObject are already out of memory. If you access any of them, it will fire the fault and pull them all back into memory again.

-willTurnIntoFault

Similar to ‑didTurnIntoFault, this method is called just before the NSManagedObject is turned into a fault. If your code needs to access attributes or relationships on the NSManagedObject before it’s turned into a fault, then this is the entry point to use. Transient resources that impact attributes and relationships should be released here.

-awakeFromInsert

As mentioned, overriding any of the ‑init methods is risky and unnecessary. However, it’s very useful to be able to prepare an NSManagedObject before it starts accepting data. Perhaps we want to set up some logical defaults or assign some relationships before handing the object to the user. In these situations, we use ‑awakeFromInsert. As the name implies, this method is called right after the NSManagedObject is created from an insert call. This method is called before any values are set and is a perfect opportunity to set default values, initialize transient properties, and perform other tasks that we would normally handle in the ‑init method. This method is called exactly once in the entire lifetime of an object. It won’t be called on the next execution of the application, and it won’t be called when an object is read in from the persistent store. Therefore, we don’t need to worry about overriding values that have been set previously. When we override this method, we should be sure to call [super awakeFromInsert] at the very beginning of our implementation to allow the NSManagedObject to finish anything it needs to before we begin our code.

-awakeFromFetch

‑awakeFromFetch is the counterpart to ‑awakeFromInsert. The ‑awakeFromFetch method will be called every time the object is retrieved from the persistent store (that is, loaded from disk). This method is highly useful for setting up transient objects or connections that the NSManagedObject will use during its life cycle. At this point in the creation of the NSManagedObject, the observation of changes to the object (or other objects) is turned off, and Core Data won’t be aware of any changes made. Ideally, we should avoid making any changes to relationships during this method because the inverse will not be set. However, if we explicitly set both sides of the relationship, it’s possible to make changes here. Like the ‑awakeFromInsert method, when we override this method, we should call [super awakeFromFetch]; before any of our own code is called.

Now that we’ve explored creating and accessing instances of NSManagedObject, we’ll look at how to retrieve them.

Building an NSFetchRequest

NSFetchRequest is the part of Core Data that causes people to think it’s a database API instead of an object hierarchy. When we want to retrieve objects from Core Data, we normally use an NSFetchRequest. It’s best to view an NSFetchRequest as a way to retrieve all instances of an entity from the object hierarchy, with the option to filter results with an NSPredicate. There are two parts to the creation of an NSFetchRequest: setting the entity to be retrieved and optionally defining an NSPredicate to filter the objects we want retrieved.

Setting the Entity

One thing that we must do as part of every NSFetchRequest is define the entity we want returned from the fetch. We do this by passing the appropriate NSEntityDescription to the NSFetchRequest. For example, if we want retrieve Recipe entities, we construct the NSFetchRequest as follows:

NSManagedObjectContext *moc = [self managedObjectContext];

NSFetchRequest *request = [[NSFetchRequest alloc] init];

[request setEntity:[NSEntityDescription entityForName:@"Recipe"

inManagedObjectContext:moc]];

In this example, we construct a new NSFetchRequest and call ‑setEntity: on it. We use the class method +entityForName:inManagedObjectContext: on the NSEntityDescription class to get the appropriate instance of NSEntityDescription back for the setter.

Executing a Fetch Request

Once we’ve constructed our NSFetchRequest, we need to execute it against the NSManagedObjectContext to get the results. Like a result set when accessing a database, an executed NSFetchRequest will return an NSArray of entities matching our search criteria. Since it’s possible that a search might fail, the execution of an NSFetchRequest accepts a pointer to an NSError to describe any problems that resulted from the execution. For example, if we want to execute the fetch from the previous example, we use the following code:

NSManagedObjectContext *moc = [self managedObjectContext];

NSFetchRequest *request = [[NSFetchRequest alloc] init];

[request setEntity:[NSEntityDescription entityForName:@"Recipe"

inManagedObjectContext:moc]];

NSError *error = nil;

NSArray *results = [moc executeFetchRequest:request error:&error];

if (error) {

NSLog(@"Error: %@\n%@", [error localizedDescription], [error userInfo]);

return;

}

In this example, we call ‑executeFetchRequest:error: on the NSManagedObjectContext, passing in the NSFetchRequest and a pointer to a local NSError. If the fetch failed with an error, the pointer will be directed to an instance of NSError that describes the problem, and the NSArray will be assigned to nil. In that situation, we dump the error to the console and return. If there’s no error, we can proceed with our code. Note that the NSArray is guaranteed to not be nil at this point, but it could be empty if no results are returned.

NSPredicate

When we don’t want every instance of an entity returned, we use an NSPredicate to narrow the search or filter the results. The NSPredicate class is quite complex and powerful and can be used for more things than just Core Data. It’s frequently used to filter the results of collection classes by acting on the KVC API and doing logic checks on the objects contained in the NSArray or NSSet.

One of the most common ways to use an NSPredicate is to construct a SQL-like query, such as the following example:

NSManagedObjectContext *moc = [self managedObjectContext];

NSFetchRequest *request = [[NSFetchRequest alloc] init];

[request setEntity:[NSEntityDescription entityForName:@"Recipe"

inManagedObjectContext:moc]];

NSPredicate *pred = [NSPredicate predicateWithFormat:@"serves > 10"];

[request setPredicate:pred];

There are many different ways to build an NSPredicate. The one shown in the previous example accepts a SQL-like NSString and can accept any number of parameters after the NSString. For example, if we were going to pass in the number of servings, we’d rewrite the NSPredicate as follows:

NSUInteger numberOfServings = 10;

NSManagedObjectContext *moc = [self managedObjectContext];

NSFetchRequest *request = [[NSFetchRequest alloc] init];

[request setEntity:[NSEntityDescription entityForName:@"Recipe"

inManagedObjectContext:moc]];

NSPredicate *pred = nil;

pred = [NSPredicate predicateWithFormat:@"serves > %i", numberOfServings];

[request setPredicate:pred];

It’s possible to add as many parameters to the NSPredicate as needed.

The NSPredicate class is quite flexible and can be used in a large number of ways. For further reading on how to use the class to its full potential, I recommend Apple’s Predicate Programming Guide.[3]

Stored Fetch Requests

In addition to constructing the NSFetchRequest directly in code, it’s possible to build them within the data model and store them for later use. By storing the fetch requests within the model itself, we can change them as needed without having to go through all the code looking for every place that they’re used. Simply changing them in the model will automatically update them wherever they’re being used. To store an NSFetchRequest within the data model, we select the entity that we want to run the request against and choose Design > Data Model > Add Fetch Request from the main menu. From there we’ll be able to set the name of the fetch request and define its predicate, as shown in the screenshot.

Once we have the fetch request in our data model, we can request a reference to it by asking the NSManagedObjectModel. Once we have a reference to the NSFetchRequest, we can execute it in the same manner as we do with an NSFetchRequest that has been constructed in code.

RecipesV1/PPRecipes/PPRSelectIngredientTypeViewController.m

NSManagedObjectContext *moc = [self managedObjectContext];

NSPersistentStoreCoordinator *psc = [moc persistentStoreCoordinator];

NSManagedObjectModel *model = [psc managedObjectModel];

NSFetchRequest *request = nil;

request = [model fetchRequestTemplateForName:@"allIngredients"];

NSMutableArray *sortArray = [NSMutableArray array];

[sortArray addObject:[[NSSortDescriptor alloc] initWithKey:@"name"

ascending:YES]];

[request setSortDescriptors:sortArray];

As shown, we call the ‑fetchRequestTemplateForName: method on the NSManagedObjectModel, which returns a fully formed NSFetchRequest to us. This NSFetchRequest will already have the NSEntityDescription and NSPredicate set, so we can execute the NSFetchRequest immediately. We can also update this NSFetchRequest to include sort descriptors if needed.

Fetched Properties

In addition to NSFetchRequest objects, we have the ability to define a lazy relationship between objects. Fetched properties are kind of a cross between relationships and the NSFetchRequest. A fetched property is not a relationship in the strictest sense and is not realized until the property is requested. When the property is accessed, Core Data performs the underlying NSFetchRequest and returns the result. Unlike a normal relationship, a fetched property is returned as an NSArray as opposed to an NSSet.

In practice, I’ve found fetched properties to be less useful and less flexible than either creating a stored fetch request or building the fetch request in code. Usually when a situation calls for a fetched property, it tends to be easier to subclass the entity in question, perform an NSFetchRequest in code, and return the results.

NSSortDescriptor

NSSortDescriptor has been around longer than Core Data, and it’s still quite useful for ordering data. As mentioned previously, data that comes from a to-many relationship is unordered by default, and it’s up to us to order it. For example, if we wanted to retrieve all the recipes and sort them by their name property in alphabetical order, we’d require an additional step as part of the fetch.

RecipesV1/PPRecipes/PPRMasterViewController.m

NSFetchRequest *fetchRequest = nil;

fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Recipe"];

NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"name"

ascending:YES];

[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];

In this example, we are retrieving all the Recipe entities by creating an NSFetchRequest with the NSEntityDescription set to our entity and no predicate. However, in addition to fetching the Recipe entities, we want them sorted. We accomplish the sorting by adding an NSArray of NSSortDescriptor instances directly to the NSFetchRequest, which cause the returned NSArray to be properly sorted.

The NSSortDescriptor takes two parameters as part of its ‑init: a key and a BOOL denoting whether the sort is ascending or descending. We can have as many NSSortDescriptor objects as we want as part of the sort, and therefore they’re placed within an NSArray prior to the sort being performed.

Adding an NSSortDescriptor is especially useful on Cocoa Touch because the NSFetchedResultsController continues to keep its results sorted without any intervention on our part. The NSFetchedResultsController is discussed in more depth in Chapter 3, iOS: NSFetchedResultsController.

Wrapping Up

We covered a large number of the important pieces of Core Data in this chapter. We discussed what is part of the Core Data stack as well as what tools we have available to access the data maintained by Core Data. As we continue to explore Core Data in depth, please use this chapter as a reference point for the aspects of Core Data and how they all fit together. By the end of the book, each of these elements should be very familiar to you.

Now that you have a firm understanding of the primary objects within Core Data, we can next explore how to best integrate Core Data with an iOS application.

Footnotes

[2]

https://developer.apple.com/library/ios/documentation/General/Conceptual/DevPedia-CocoaCore/KeyValueCoding.html.

[3]

http://developer.apple.com/documentation/Cocoa/Conceptual/Predicates/Articles/pUsing.html

Copyright © 2016, The Pragmatic Bookshelf.

Chapter 3

iOS: NSFetchedResultsController

The NSFetchedResultsController was introduced alongside Core Data when the framework was added to iOS with version 3.0. Since its introduction, developers have settled into a love--hate relationship with this class. When it’s used in the way it was intended, it works extremely well. The hate part comes in when developers attempt to use the NSFetchedResultsController outside of its intended niche—that’s when things start to fall apart quickly.

The NSFetchedResultsController is designed to be the glue between Core Data and the UITableView. When Core Data was first added to iOS, the Core Data team realized that there was a significant amount of work to get Core Data and table views talking well. They removed nearly all of that work with the introduction of the NSFetchedResultsController.

In this chapter, we discuss what the NSFetchedResultsController is designed to do and how it works. Once you have a handle on how it works, we’ll explore alternatives so you know what to use when NSFetchedResultsController is not the correct fit.

How to Use the NSFetchedResultsController

When Core Data was added to iOS, it was clear to the Core Data team that Core Data and the UITableView would be used together extensively. They also realized that getting these two pieces to work together smoothly would require a fair amount of code that could be abstracted away; that abstraction is the NSFetchedResultsController. The NSFetchedResultsController is the glue that binds a UITableView to Core Data so that we need to write very little code.

The purpose of the NSFetchedResultsController is twofold. The NSFetchedResultsController is designed to retrieve data from Core Data and store that data for access. It does this with an internal NSFetchRequest that it uses for the retrieval. It then stores the data and makes it available for use. As part of the storage and retrieval, the NSFetchedResultsController organizes the returned data into sections, in the process making the data more useful to a UITableView.

The NSFetchedResultsController’s second purpose is to monitor changes in the data. Without the capability to be notified when the data has changed, the NSFetchedResultsController wouldn’t be much more use than an NSArray. When the NSManagedObjectContext associated with the NSFetchedResultsController changes, the NSFetchedResultsController checks to see whether any of the objects it’s referencing are impacted. Further, it watches inserts to determine whether any newly inserted objects should be included in what’s being referenced. If any changes occur, the NSFetchedResultsController notifies its delegate of the changes. The delegate is normally its associated UITableView.

Standing Up an NSFetchedResultsController

The creation of a NSFetchedResultsController takes a number of steps and uses several of the classes that we discussed in Chapter 2, Under the Hood of Core Data.

RecipesV1/PPRecipes/PPRMasterViewController.m

- (NSFetchedResultsController *)fetchedResultsController

{

if (fetchedResultsController) return fetchedResultsController;

NSManagedObjectContext *moc = [self managedObjectContext];

NSFetchRequest *fetchRequest = nil;

fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Recipe"];

NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"name"

ascending:YES];

[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];

NSFetchedResultsController *frc = nil;

frc = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest

managedObjectContext:moc

sectionNameKeyPath:nil

cacheName:@"Master"];

[self setFetchedResultsController:frc];

[[self fetchedResultsController] setDelegate:self];

NSError *error = nil;

ZAssert([[self fetchedResultsController] performFetch:&error],

@"Unresolved error %@\n%@", [error localizedDescription],

[error userInfo]);

return fetchedResultsController;

}

The NSFetchedResultsController is effectively a wrapper around an NSFetchRequest. Therefore, we first need to construct the NSFetchRequest that will be used. In this example, we’re building an NSFetchRequest that retrieves all of the available recipes. Further, we’re going to sort the Recipe entities based on their name attribute.

Once we’ve built the NSFetchRequest, we construct the NSFetchedResultsController. In its initialization, it accepts an NSFetchRequest, an NSManagedObjectContext, an NSString for its sectionNameKeyPath, and another NSString for its cacheName. Let’s explore each of these in turn.

NSFetchRequest

The NSFetchRequest retrieves the data from Core Data and makes it available for use. This is the NSFetchRequest we just defined in code.

NSManagedObjectContext

The NSFetchedResultsController requires an NSManagedObjectContext to perform the fetch against. Additionally, this is the NSManagedObjectContext that the NSFetchedResultsController will be monitoring for changes. Note that the NSFetchedResultsController is designed to work against user interface elements, and therefore, it works best when it’s pointed at an NSManagedObjectContext that’s running on the main/UI thread. Threading is discussed in more depth in Chapter 6, Threading.

sectionNameKeyPath

The NSFetchedResultsController uses the sectionNameKeyPath to break the retrieved data into sections. Once the data is retrieved, the NSFetchedResultsController calls for a property on each entity using KVC (more on this in Chapter 10, OS X: Bindings, KVC, and KVO). The value of that property will be used to break the data into sections. In our current example, we have this set to nil, which means our data will not be broken into sections. However, we could easily add it, as follows:

RecipesV1/PPRecipes/PPRMasterViewController.m

if (fetchedResultsController) return fetchedResultsController;

NSManagedObjectContext *moc = [self managedObjectContext];

NSFetchRequest *fetchRequest = nil;

fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Recipe"];

NSMutableArray *sortArray = [NSMutableArray array];

[sortArray addObject:[[NSSortDescriptor alloc] initWithKey:@"type"

ascending:YES]];

[sortArray addObject:[[NSSortDescriptor alloc] initWithKey:@"name"

ascending:YES]];

[fetchRequest setSortDescriptors:sortArray];

NSFetchedResultsController *frc = nil;

frc = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest

managedObjectContext:moc

sectionNameKeyPath:@"type"

cacheName:@"Master"];

Something to note here is that along with passing in @"type" to the initialization of the NSFetchedResultsController, we also added a second NSSortDescriptor to the NSFetchRequest. The NSFetchedResultsController requires the data to be returned in the same order as it will appear in the sections. As a result, we must sort the data first by type and then by name.

cacheName

The last property of the initialization of the NSFetchedResultsController is the cacheName. This value is used by the NSFetchedResultsController to build up a small data cache on disk. That cache will allow the NSFetchedResultsController to skip the NSPersistentStore entirely when its associated UITableView is reconstructed. This cache can dramatically improve the launch performance of any associated UITableView.

However, this cache is extremely sensitive to changes in the data and the NSFetchRequest. Therefore, this cache name cannot be reused from one UITableView to another, nor can it be reused if the NSPredicate changes.

Once the NSFetchedResultsController has been initialized, we need to populate it with data. This can be done immediately upon initialization, as in our current example, or it can be done later. When to populate the NSFetchedResultsController is more of a performance question. If the associated UITableView is constructed very early, we may want to wait to populate the NSFetchedResultsController until the UITableView is about to be used. For now and until we can determine there’s a performance issue, we’ll populate it upon initialization. This is done with a call to ‑performFetch:, which takes a pointer to an NSError variable. If there’s an error in the fetch, the NSError will be populated, and the call will return NO.

Wiring the NSFetchedResultsController to a UITableView

Now that we have our NSFetchedResultsController initialized, we need to wire it into its associated UITableView. We do this within the various UITableViewDatasource methods. At a minimum, we need to implement two of them but implementing the core three is better.

RecipesV1/PPRecipes/PPRMasterViewController.m

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

{

return [[[self fetchedResultsController] sections] count];

}

The first one is ‑numberOfSectionsInTableView:. Here we ask the NSFetchedResultsController to return its array of sections, and we return the count of them. If we don’t have a sectionNameKeyPath set on the NSFetchedResultsController, there will be either zero or one section in that array. In previous versions of iOS (prior to iOS 4.x), the UITableView did not like being told there were zero sections. You may run across older code that checks the section count and always returns a minimum of one section (with zero rows). That issue has been addressed, and the associated check is no longer needed.

RecipesV1/PPRecipes/PPRMasterViewController.m

- (NSInteger)tableView:(UITableView *)tableView

numberOfRowsInSection:(NSInteger)section

{

NSArray *sections = [[self fetchedResultsController] sections];

id <NSFetchedResultsSectionInfo> sectionInfo = nil;

sectionInfo = [sections objectAtIndex:section];

return [sectionInfo numberOfObjects];

}

The tableView: numberOfRowsInSection: method is slightly more complex. Here, we grab the array of sections, but we also grab the object within the array that’s at the index being passed into the method. There’s no need to check to see whether the index is valid since the ‑numberOfSectionsInTableView: method is the basis for this index. The object that’s in the NSArray is undetermined but guaranteed to respond to the NSFetchedResultsSectionInfo protocol. One of the methods on that protocol is numberOfObjects, which we use to return the number of rows in the section.

RecipesV1/PPRecipes/PPRMasterViewController.m

- (UITableViewCell *)tableView:(UITableView *)tableView

cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

UITableViewCell *cell = nil;

NSManagedObject *object = nil;

object = [[self fetchedResultsController] objectAtIndexPath:indexPath];

cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];

[[cell textLabel] setText:[object valueForKey:@"name"]];

return cell;

}

In the ‑tableView: cellForRowAtIndexPath: method, we use another very useful ability of the NSFetchedResultsController: the ‑objectAtIndexPath: method. With this method, we can retrieve the exact object we need to work with in a single call. This reduces the complexity of our ‑tableView: cellForRowAtIndexPath: method significantly.

There are many additional examples of how to wire in the NSFetchedResultsController to the UITableView, but these three highlight the most common usage. Even with just these three methods, you can see how the NSFetchedResultsController drastically reduces the amount of code you need to write (and thereby maintain) to access the data to be displayed.

Listening to the NSFetchedResultsController

In addition to making it very easy for us to retrieve and display the data for a UITableView, the NSFetchedResultsController makes it relatively painless to handle changes in that data. If the values within one of our recipes changes (perhaps through iCloud, as discussed in Chapter 8, Using Core Data with iCloud, or through an import), we want our UITableView to immediately reflect those changes. In addition, if a recipe is removed or added, we want our UITableView to be accurate. To make sure these updates happen, we must add the delegate methods for the NSFetchedResultsControllerDelegate protocol. As mentioned, it’s common for the UIViewController to also be the delegate for the NSFetchedResultsController. There are five methods in this protocol; let’s take a look at each of them.

-controllerWillChangeContent:

The first method, ‑controllerWillChangeContent:, tells us that changes are about to start. This method is our opportunity to instruct the UITableView that changes are coming. Typically this is where we tell the UITableView to stop updating the user interface so that all of the changes can be displayed at once.

RecipesV1/PPRecipes/PPRMasterViewController.m

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller

{

[[self tableView] beginUpdates];

}

-controller: didChangeSection: atIndex: forChangeType:

This method is called when a section changes. The only valid change types are NSFetchedResultsChangeInsert and NSFetchedResultsChangeDelete. This is our opportunity to tell the UITableView that a section is being added or removed.

RecipesV1/PPRecipes/PPRMasterViewController.m

- (void)controller:(NSFetchedResultsController *)controller

didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo

atIndex:(NSUInteger)sectionIndex

forChangeType:(NSFetchedResultsChangeType)type

{

NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:sectionIndex];

switch(type) {

case NSFetchedResultsChangeInsert:

[[self tableView] insertSections:indexSet

withRowAnimation:UITableViewRowAnimationFade];

break;

case NSFetchedResultsChangeDelete:

[[self tableView] deleteSections:indexSet

withRowAnimation:UITableViewRowAnimationFade];

break;

case NSFetchedResultsChangeUpdate:

case NSFetchedResultsChangeMove:

break;

}

}

Here we use a switch to determine what the change type is and pass it along to the UITableView. You may note that there are two case statements at the end of this that don’t react. They’re added to satisfy the compiler and at this time aren’t being used in this method.

-controller: didChangeObject: atIndexPath: forChangeType: newIndexPath:

This is the most complex method in the NSFetchedResultsControllerDelegate protocol. In this method, we’re notified of any changes to any data object. The four types of changes that we must react to are listed next.

RecipesV1/PPRecipes/PPRMasterViewController.m

- (void)controller:(NSFetchedResultsController *)controller

didChangeObject:(id)anObject

atIndexPath:(NSIndexPath *)indexPath

forChangeType:(NSFetchedResultsChangeType)type

newIndexPath:(NSIndexPath *)newIndexPath

{

NSArray *newArray = nil;

NSArray *oldArray = nil;

if (newIndexPath) newArray = [NSArray arrayWithObject:newIndexPath];

if (indexPath) oldArray = [NSArray arrayWithObject:indexPath];

switch(type) {

case NSFetchedResultsChangeInsert:

[[self tableView] insertRowsAtIndexPaths:newArray

withRowAnimation:UITableViewRowAnimationFade];

break;

case NSFetchedResultsChangeDelete:

[[self tableView] deleteRowsAtIndexPaths:oldArray

withRowAnimation:UITableViewRowAnimationFade];

break;

case NSFetchedResultsChangeUpdate:

{

UITableViewCell *cell = nil;

NSManagedObject *object = nil;

cell = [[self tableView] cellForRowAtIndexPath:indexPath];

object = [[self fetchedResultsController] objectAtIndexPath:indexPath];

[[cell textLabel] setText:[object valueForKey:@"name"]];

break;

}

case NSFetchedResultsChangeMove:

[[self tableView] deleteRowsAtIndexPaths:oldArray

withRowAnimation:UITableViewRowAnimationFade];

[[self tableView] insertRowsAtIndexPaths:newArray

withRowAnimation:UITableViewRowAnimationFade];

break;

}

}

An NSFetchedResultsChangeInsert is fired when a new object is inserted that we need to display in our UITableView. When we receive this call, we pass it along to the UITableView and tell the table view what type of animation to use.

An NSFetchedResultsChangeDelete is fired when an existing object is removed. Just as we do with an insert, we pass this information along to the UITableView and tell it what type of animation to use when removing the row.

An NSFetchedResultsChangeUpdate is fired when an existing object has changed internally—in other words, when one of its attributes has been updated. We don’t know from this call whether it’s an attribute that we care about. Instead of spending time determining whether we should update the row, it’s generally cheaper to just update the row.

An NSFetchedResultsChangeMove is fired when a row is moved. The move could result from a number of factors but is generally caused by a data change resulting in the row being displayed in a different location. In our example, if the name or type of a recipe were altered, it would most likely cause this change type. It is completely possible—and quite common—to receive an NSFetchedResultsChangeMove and an NSFetchedResultsChangeUpdate in the same batch of changes. When this change type is received, we make two calls to the UITableView: one to remove the row from its previous location and another to insert it into its new location.

-controller: sectionIndexTitleForSectionName:

We use this method when we want to massage the data coming back from our NSFetchedResultsController before it’s passed to the UITableView for display. One situation where this might be necessary is if we want to remove any extended characters from the title before it is displayed; another example is if we want to add something to the displayed title that’s not in the data.

RecipesV1/PPRecipes/PPRMasterViewController.m

- (NSString*)controller:(NSFetchedResultsController*)controller

sectionIndexTitleForSectionName:(NSString*)sectionName

{

return [NSString stringWithFormat:@"[%@]", sectionName];

}

-controllerDidChangeContent:

The final method tells us that this round of changes is finished and we can tell the UITableView to update the user interface. We can also use this method to update any other parts of the user interface outside of the UITableView. For example, if we had a count of the number of recipes displayed, we would update that count here.

RecipesV1/PPRecipes/PPRMasterViewController.m

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller

{

[[self tableView] endUpdates];

}

With the implementation of the five methods described earlier, our UITableView can now retrieve, display, and update its display without any further work from us. In fact, a large portion of the code in the NSFetchedResultsControllerDelegate methods is fairly boilerplate and can be moved from project to project, further reducing the amount of “new” code we need to maintain.

Under the Hood of the NSFetchedResultsController

We can see the value of an NSFetchedResultsController, but how does it actually work? When I started to explore the details, I was shocked to find out that I could duplicate much of the behavior of the NSFetchedResultsController using publicly available APIs.

This means that instead of trying to cram the NSFetchedResultsController into places that it almost fits (or worse) you can build your own that behaves exactly in the way that you need to. With publicly exposed APIs, the Core Data team has invited us to build our own observers of Core Data so that non-table view user interfaces can react to changes in the data easily.

At its core, the NSFetchedResultsController takes advantage of the notifications that an NSManagedObjectContext fires off. When we initialize the NSFetchedResultsController, it sets itself up as an observer and then reacts as the notifications come in.

NSManagedObjectContextObjectsDidChangeNotification

One of the three notifications that the NSFetchedResultsController listens for is the NSManagedObjectContextObjectsDidChangeNotification. This very chatty notification tells the NSFetchedResultsController whenever one of the attributes of any object has changed. The NSFetchedResultsController uses this information, combined with its NSFetchRequest, to determine whether it needs to notify its delegate of the changes. These changes often result in a call to ‑controller: didChangeObject: atIndexPath: forChangeType: newIndexPath:, with a change type of NSFetchedResultsChangeUpdate and/or NSFetchedResultsChangeMove.

NSManagedObjectContextWillSaveNotification

Without access to the source code of the NSFetchedResultsController, I can’t say with 100 percent certainty that this notification is used; however, it appears that NSManagedObjectContextWillSaveNotification is being used to catch when an object is deleted. When an object is deleted, the NSFetchedResultsController determines whether it’s relevant to our NSFetchRequest and issues the appropriate delegate callbacks. This would typically result in a call to ‑controller: didChangeSection: atIndex: forChangeType: if the deletion caused a section to disappear and/or a call to the ‑controller: didChangeObject: atIndexPath: forChangeType: newIndexPath: with a change type of NSFetchedResultsChangeDelete.

NSManagedObjectContextDidSaveNotification

The final NSNotification type is fired after the NSManagedObjectContext has completed its save. This notification is observed so that the NSFetchedResultsController can capture any objects that are newly inserted or have been changed in another context and propagated (as discussed in Chapter 6, Threading). This would typically result in a call to ‑controller: didChangeSection: atIndex: forChangeType: if the insertion/update caused a section to disappear or appear. It would also cause a call to ‑controller: didChangeObject: atIndexPath: forChangeType: newIndexPath: with any of the change types available.

Beyond listening for these notifications, the NSFetchedResultsController is just a container. The cache is most likely a serialization of the currently fetched objects (although I haven’t figured out its exact data structure yet).

Why is this information valuable? For one, it’s always helpful to understand how things work so that when they stop working, we can investigate them and resolve the issue. In addition, since the NSFetchedResultsController has such an extremely narrow focus, we don’t want to use it in situations where we don’t have a UITableView to populate. However, it’s so useful that we really want to use some of its features outside of this narrow focus. With an understanding of how it works comes the ability to duplicate the features that are useful.

Building Our Own: ZSContextWatcher

Since the introduction of the NSFetchedResultsController, I’ve run into numerous situations in which I wanted to use its ability to detect data changes even when I wasn’t using a UITableView. Building a user interface for an iPad where three different entities are being displayed at once is a situation that begs for some kind of watcher to notify the user interface when the data has changed. Needing to watch not just a single type of entity but also relationships associated with that entity is another place where an observer is useful. Frequently I would attempt to use an NSFetchedResultsController and run into one problem or another that made it more difficult than it needed to be. This led me to investigate how the NSFetchedResultsController worked and finally resulted in the creation of the ZSContextWatcher.

The ZSContextWatcher is publicly available under the BSD license, and the latest version is always in my public GitHub repository at http://github.com/mzarra/ZDS_Shared.

The goal of the ZSContextWatcher is to provide us with the ability to monitor a subset of the data that’s in Core Data and to be notified when it changes. It’s the same functionality that’s in the NSFetchedResultsController but not as tightly coupled with the UITableView.

ZSContextWatcher/ZSContextWatcher.h

@interface ZSContextWatcher : NSObject

- (id)initWithManagedObjectContext:(NSManagedObjectContext*)context;

- (void)addEntityToWatch:(NSEntityDescription*)description

withPredicate:(NSPredicate*)predicate;

@end

The API to use this class is composed of an initializer and two methods.

-initWithManagedObjectContext:

We initialize the ZSContextWatcher with an NSManagedObjectContext. This NSManagedObjectContext is used when it sets itself up as an observer on NSNotificationCenter. This avoids notifications coming from other NSManagedObjectContext instances.

ZSContextWatcher/ZSContextWatcher.m

- (id)initWithManagedObjectContext:(NSManagedObjectContext*)context;

{

ZAssert(context, @"Context is nil!");

if (!(self = [super init])) return nil;

NSNotificationCenter *center = [NSNotificationCenter defaultCenter];

[center addObserver:self

selector:@selector(contextUpdated:)

name:NSManagedObjectContextDidSaveNotification

object:context];

return self;

}

-addEntityToWatch: withPredicate:

The second method in the public API for the ZSContextWatcher allows us to define what the watcher is listening for. This is moved away from the initialization because I wanted the ability to watch more than one entity and/or more than one predicate. With this method, I can add as many entities and/or predicates as I need.

ZSContextWatcher/ZSContextWatcher.m

- (void)addEntityToWatch:(NSEntityDescription*)description

withPredicate:(NSPredicate*)predicate;

{

NSPredicate *entityPredicate = nil;

NSPredicate *final = nil;

NSArray *array = nil;

entityPredicate = [NSPredicate predicateWithFormat:@"entity.name == %@",

[description name]];

array = [NSArray arrayWithObjects:entityPredicate, predicate, nil];

final = [NSCompoundPredicate andPredicateWithSubpredicates:array];

if (![self masterPredicate]) {

[self setMasterPredicate:finalPredicate];

return;

}

array = [NSArray arrayWithObjects:[self masterPredicate], final, nil];

finalPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:array];

[self setMasterPredicate:finalPredicate];

}

We do a bit of NSPredicate construction in the implementation. First, we take the passed-in NSEntityDescription and use that inside a new predicate that compares the entity name. Next, we create a compound predicate that combines the passed-in predicate with the newly created entity predicate with an AND join. Now we have a new predicate that checks to make sure the compared object is the same entity before we use the second part of the predicate against the object.

Why do we do this? If we just run the passed-in predicate against every object, we’d get an error when it hits an object that doesn’t have one of the properties in the predicate. By adding a prefix predicate that checks the name of the entity, we’re ensuring it will run only against the correct entity.

If there’s no existing predicate in our ZSContextWatcher, we set our new compound predicate as the masterPredicate and return. However, if there’s already a masterPredicate set, we must compound the existing predicate with our new one. Again, we use an NSCompoundPredicate to combine the existing masterPredicate and our new predicate. However, this time we use an OR instead of an AND in the compound predicate. Finally, we take the newly created compound predicate and set that as the masterPredicate.

-contextUpdated:

We’ve constructed a predicate that we can run against a collection of NSManagedObject instances, and it will filter out any objects that we don’t care about. Now when we receive a notification from an NSManagedObjectContextDidSaveNotification, we can easily filter the incoming objects against our predicate.

ZSContextWatcher/ZSContextWatcher.m

- (void)contextUpdated:(NSNotification*)notification

{

NSInteger totalCount = 0;

NSSet *temp = nil;

temp = [[notification userInfo] objectForKey:NSInsertedObjectsKey]

NSMutableSet *inserted = [temp mutableCopy];

if ([self masterPredicate]) {

[inserted filterUsingPredicate:[self masterPredicate]];

}

totalCount += [inserted count];

temp = [[notification userInfo] objectForKey:NSDeletedObjectsKey];

NSMutableSet *deleted = [temp mutableCopy];

if ([self masterPredicate]) {

[deleted filterUsingPredicate:[self masterPredicate]];

}

totalCount += [deleted count];

temp = [[notification userInfo] objectForKey:NSUpdatedObjectsKey];

NSMutableSet *updated = [temp mutableCopy];

if ([self masterPredicate]) {

[updated filterUsingPredicate:[self masterPredicate]];

}

totalCount += [updated count];

if (totalCount == 0) {

return;

}

NSMutableDictionary *results = [NSMutableDictionary dictionary];

if (inserted) {

[results setObject:inserted forKey:NSInsertedObjectsKey];

}

if (deleted) {

[results setObject:deleted forKey:NSDeletedObjectsKey];

}

if (updated) {

[results setObject:updated forKey:NSUpdatedObjectsKey];

}

if ([[self delegate] respondsToSelector:[self action]]) {

[[self delegate] performSelectorOnMainThread:[self action]

withObject:self

waitUntilDone:YES];

}

}

When we receive a notification, we need to check the ‑userInfo and see whether there are any objects that we care about. In the ‑userInfo, there are up to three NSSet instances: one for updated objects, one for deleted objects, and one for inserted objects. We walk through each of these sets, grabbing a mutable copy of each one, filtering the mutable set against our masterPredicate, and keeping track of how many objects are left. If no objects are left at the end of the filtering, we know there were none in the save that we cared about, and we can return.

If any objects are left, we have to notify our delegate about them. Since we’ve already filtered the objects, we may as well pass them to our delegate so our delegate doesn’t need to repeat the work. We create a new NSDictionary and add each of our NSSet instances to it using the same keys that the incoming NSNotification used. Once it’s constructed, we can pass the newly created NSDictionary off to our delegate.

Now we have a class that allows us to watch any NSManagedObjectContext of our choosing and notifies us if an object that we care about has been touched in any way. We can make the predicate as broad or narrow as we want. By allowing the delegate to pass in the predicate, we’ve also made this class highly reusable.

Wrapping Up

The NSFetchedResultsController is a great time-saver when we’re working with a UITableView. It can drastically reduce the amount of code we need to write and make our UITableView instances perform very well. However, it has a narrowly focused purpose, and it should be avoided when you aren’t working with a UITableView. Fortunately, with a little bit of effort, we can duplicate quite a bit of its functionality and create a highly flexible class that fits into a variety of situations.

Copyright © 2016, The Pragmatic Bookshelf.

Chapter 4

Versioning and Migration

Just like a battle plan, no codebase ever survives contact with users. As soon as users start to use an application, they want to change it. Even if the code is just for ourselves, we, also as users, will want to change things. For example, we may need to add an attribute or a new object and then restructure things to accommodate those changes. Additions and changes can be quite involved and invariably require a change in how the data is stored.

Starting with Mac OS X 10.5 Leopard and iOS 3.0, Apple has made data migration nearly trivial for users of Core Data. Taking the project outlined in Chapter 1, Building a Foundation, we’ll add some additional features to it in succeeding versions. Although a data migration works even when there’s no data stored, it’s more useful to have some data to work with. Therefore, if you haven’t added any recipes yet, I recommend doing so before we proceed.

In version 2, we’ll add the ability to tag an author to a recipe as well as tag a “last used” date. That way, we know who created the delicious dish as well as the last time we made it. We certainly wouldn’t want to accidentally make the same dish two days in a row!

In version 3, we’ll normalize the repository a bit by extracting the ingredients and forming a many-to-many relationship back to the recipes. In addition, we’ll add the concept of a shopping list to make it easier to ensure we pick up all the ingredients on our next trip to the store. Next, we’ll extract the unitOfMeasure attribute from the RecipeIngredient entity into its own entity and allow that new entity to be linked to the new ingredient entity. This step gives us one lookup list for the various units of measure and reduces the risk of human error. Lastly, we’ll remove the Meat and Fish entries from the Type attribute of the Recipe entity. Any recipe entries that are flagged with Meat or Fish will be updated to Entrée instead.

Some Maintenance Before We Migrate

Before we actually release a new version of our application that migrates the data, we need to first complete a minor “maintenance” update for our users. Normally, we would add this code to the very first version of our application, but just in case we wrote that first release before versioning was a consideration, we need to go back to our old version and add a very small amount of code to help our users.

Some users will download the new version of an application to just “try it out” and see whether it’s worth the upgrade price or worth the hassle. Normally this isn’t an issue—until we upgrade the data underneath our users. Then things go sideways. What we do not want to happen is the error message shown here.

Note that this is the error message we would see on OS X. On iOS, our application would simply crash on launch. This is a terrible user experience and something we want to avoid. Fortunately, the way to avoid it is very easy, and we can add it to a point release of our application before we do any migration. That way, when the users open the first version of our application after “testing” the second version, they get a friendly error message. Or we can take it a step further and restore/access the older version of their data.

Baseline/PPRecipes/PPRAppDelegateAlt2.m

dispatch_async(queue, ^{

NSError *error = nil;

NSPersistentStoreCoordinator *coordinator = nil;

coordinator = [moc persistentStoreCoordinator];

NSPersistentStore *store = nil;

store = [coordinator addPersistentStoreWithType:NSSQLiteStoreType

configuration:nil

URL:storeURL

options:nil

error:&error];

if (!store) {

ALog(@"Error adding persistent store to coordinator %@\n%@",

[error localizedDescription], [error userInfo]);

NSString *msg = nil;

msg = [NSString stringWithFormat:@"The recipes database %@%@%@\n%@\n%@",

@"is either corrupt or was created by a newer ",

@"version of Grokking Recipes.  Please contact ",

@"support to assist with this error.",

[error localizedDescription], [error userInfo]];

UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Error"

message:msg

delegate:self

cancelButtonTitle:@"Quit"

otherButtonTitles:nil];

[alertView show];

return;

}

This is the new creation of the persistent store inside the ‑initializeCoreDataStack method. Once we’ve initialized our persistent store coordinator, we kick off a background process to add the NSPersistentStore to the NSPersistentStoreCoordinator. If there’s an error here, we need to present it to the user and halt the application. We do this with a UIAlertView showing the error to the user, and then the delegate of the UIAlertView forces the application to quit. The resulting error message is shown here. In a production application, we would perhaps offer the user the option to reset the data as opposed to exiting. Note that we also have an ALog before the UIAlertView. When we’re developing this application, we want to make very sure that a developer-level error fires here to ideally avoid the risk of the user ever encountering it.

A Simple Migration

To demonstrate a simple migration, let’s add the ability to attribute recipes to authors. To begin the versioning, the first thing we need to do is create a new managed object model (MOM) based on the first one. To do that, we select the existing model in Xcode and then choose Design > Data Model > Add Model Version.

Your First Data Model Version

When you first set up versioning, be sure to look inside the target in Xcode and update the Compile Sources section. If you don’t see the .xcdatamodeld file inside the target, remove the xcdatamodel references from it and drag the entire xcdatamodeld bundle into the target. Otherwise, your application may complain about being unable to merge entities because it will treat each version of the model as an independent model.

Once this change has been completed, it’s best to clean the project (delete any previously compiled code) by choosing Product > Clean from the main menu.

Creating a Versioned Data Model

This is the first time we’ve added a model version, so Xcode is going to create a new bundle for us called PPRecipes.xcdatamodeld and put the original MOM inside the bundle along with a new copy of the original MOM. To make things more clear in the example project, I renamed these MOM objects to v1.xcdatamodel and v2.xcdatamodel. Select the PPRecipes.xcdatamodeld file and open the File Inspector Utility View (⌘⌥1). In the utility view, you’ll see a Versioned Core Data Model section. (The Xcode templates have been bouncing back and forth on this issue over the past few versions. It’s possible, depending on when you created your project, that you already have a versioned data model.) Inside of that section is a Current option, allowing us to select which model file is the current one. Make sure it references v2, as shown in the screenshot.

Now that we have a new version of the MOM, it’s time to add the new entities and attributes. This process requires the addition of a new entity and some changes to the Recipe entity. Comparing the v1 data model and the v2 data model (as shown), you can see that we added a new Author entity along with its one-to-many relationship with the Recipe entity. Also, the Recipe entity has a new attribute called lastUsed, which is defined as a Date.

We’re not quite done. If we were to run the application right now, we would trip the error that we discussed in Some Maintenance Before We Migrate. Clearly, something is missing.

Turning On Automatic Data Migration

The first thing we need to do is to tell Core Data to automatically migrate data when the persistent store isn’t using the same model as the current version. To do this, we’ll make a small change to the persistentStoreCoordinator method in our AppDelegate. Previously we were passing nil to the addPersistentStoreWithType:configuration:URL:options:error: method for the options parameter. However, we need to change that to the following:

RecipesV2/PPRecipes/PPRAppDelegate.m

NSMutableDictionary *options = [NSMutableDictionary dictionary];

[options setValue:[NSNumber numberWithBool:YES]

forKey:NSMigratePersistentStoresAutomaticallyOption];

[options setValue:[NSNumber numberWithBool:YES]

forKey:NSInferMappingModelAutomaticallyOption];

NSPersistentStore *store = nil;

store = [coordinator addPersistentStoreWithType:NSSQLiteStoreType

configuration:nil

URL:storeURL

options:options

error:&error];

The first of these options, NSMigratePersistentStoresAutomaticallyOption, tells Core Data to attempt a migration automatically if it determines that one is needed.

The second option, NSInferMappingModelAutomaticallyOption, instructs Core Data to resolve the mapping between the persistent store and the current model. If Core Data can figure this out, it will perform the migration.

For this migration, Core Data can easily resolve the changes that need to be made and will be able to do an inferred (automatic) migration for us. When the changes are more severe, we do a heavy, manual migration, as discussed in A Heavy/Manual Migration.

With those changes made, we can run our application, and Core Data automatically handles the migration for us and updates the persistent store to the new model.

The Difference Between Light and Heavy Migrations

There are two types of migrations for Core Data: light (also referred to as lightweight or inferred) migration and heavy (also referred to as manual) migration. When we’re working with SQLite persistent stores, the difference between these two types of migration is significant.

A light migration of a SQLite persistent store occurs within the SQLite file itself. There are no objects loaded into memory, and therefore the memory requirements of the migration are quite low. In addition, since the migration is occurring within the database file, it is very fast. While the size of the database file still affects the speed of the migration, a light migration of a large database will still be remarkably faster than a heavy migration of a small database.

The speed and memory differences are so large that I recommend avoiding heavy migrations at nearly any cost.

A heavy migration is far more complicated than a light migration. When we perform a heavy migration, we must load each entity into memory, translate it from the old store to the new store, and then write it back out to disk. This requires two Core Data “stacks” in memory at the same time and a large amount of data processing in our application. It takes time—a lot of time. When a heavy migration is required, the application normally needs to show a wait dialog to the user so that the user knows what’s going on.

In addition to the computational time, a heavy migration requires more work from the developer. When we’re performing a light migration, we pass two options to Core Data and let the framework do the work. When we perform a heavy migration, we must explain the migration to Core Data. This requires us to create a mapping model, configure each entity in the mapping model, and sometimes even write code specific to a migration. As a result, there’s a significant maintenance cost for the developer on top of the computational cost. All the same, it’s a situation that may arise, so let’s take a look at a heavy migration.

A Heavy/Manual Migration

A heavy migration is required when we go outside of the bounds of what a light migration can accomplish. A simple example of something that goes beyond a light migration is a logic-based migration. Imagine a situation in which, as part of a migration/application update, we need to change the data that’s in the database. Perhaps there’s a typo in the included data or a change in the logic. Changing the data during the migration is outside of the scope of a light migration. However, we can easily add it to a heavy migration.

A more complex example would be a situation that involves normalizing data. In our application, each recipe has one or more recipe ingredients. If we wanted to expand our application and extract the common parts of the recipe ingredient into new tables, we’d be stepping outside the boundaries of what a light migration can accomplish.

In this migration, we’re going to accomplish two goals. First we’ll massage the data during the migration and find every occurrence of Meat or Fish for the recipe type and replace it with Entrée. In addition, we’ll create new entities: Ingredient and UnitOfMeasure. During the migration of the RecipeIngredient entity, we’ll create or associate the appropriate Ingredient to the RecipeIngredient.

Creating Our First Mapping Model

The first step is to create a mapping model for this migration. A mapping model is a description of how the migration is supposed to work. First we create a new file in Xcode, and select the section for Core Data. In that section, there’s a template called Mapping Model, as shown here. Once we select that template, Xcode asks us to choose the data model version to use as the source. For this migration, we’re going from v2 to v3, so we select v2. Then Xcode asks us to select the destination model; we’ll select v3. Finally, we must name the mapping model. I named it the very descriptive FromV2toV3.

Now Xcode will do a best guess at the migration from v2 to v3 and display its results, as shown next. There’s a lot of information in this view; let’s go through it piece by piece. As we step through this view, keep in mind that the view represents the migration from the perspective of the destination model. Everything is described as coming from the source into the destination.

The entity mappings are on the left side of the view, just to the right of the project list. Each item in this list represents a part of the migration that will occur. They’re not one-to-one with the number of entities that we have; we can actually have more or fewer mappings than we have entities. Specifically, as we’ll see when we update this mapping model, we can have more than one mapping for an entity. In addition, if an entity is no longer represented in the new model, it may not have a mapping at all.

To the right of the entity mappings are the attribute and relationship mappings. The attribute mappings are located at the top of the view. This section of the view describes how the attributes of an entity are mapped from the source to the destination. Since we’re looking at this from the perspective of the destination, this list includes every attribute that exists in the destination entity. Xcode has already prepopulated the attributes and taken a guess at where the attributes come from. Reviewing what has already been populated by Xcode, we can see how the attribute mappings work. Several variables are available for these mappings:

- $manager, which references the NSMigrationManagerKey

- $source, which references the NSMigrationSourceObjectKey

- $destination, which references the NSMigrationDestinationObjectKey

- $entityMapping, which references the NSMigrationEntityMappingKey

- $propertyMapping, which references the NSMigrationPropertyMappingKey

- $entityPolicy, which references the NSMigrationEntityPolicyKey

The relationship mappings are below the entity mappings. Like the attribute mappings, these resolve the relationships for the destination entity. Normally, these mappings resolve to an instance of an entity in the destination store that existed in the source store. To accomplish this, the mapping for the object at the other end of the relationship must be higher in the list (the list is migrated in order, top to bottom). Then it’s a matter of configuring the mapping properly, as shown here.

Customizing the Heavy/Manual Migration

So far, the migration we built doesn’t do very much: it migrates the entities from the old store to the new store. We need to adjust the migration to make it perform the more complicated aspects of the migration, the ones that are beyond the abilities of the light migration.

The first part of this customization is to change the Type attribute on recipes that are currently set as Fish or Meat. To do this, we’ll utilize the filter feature of the migration. First, select the RecipeToRecipe mapping. Next, open the Mapping Model inspector, which is utility view 3 in Xcode (⌘⌥3). In that inspector, we see a Filter Predicate field. This filter determines which entities are migrated. Utilizing a filter, we can migrate only the recipe entities that have their type set to something other than Fish or Meat. The setting is shown in the screenshot.

However, this leaves our meat and fish recipes unmigrated. To include them, we first duplicate the RecipeToRecipe mapping (unfortunately by hand with the current version of Xcode) and change the filter to be inclusive of Meat and Fish. Then, in this second mapping, we change the Value Expression setting for the Type attribute to Entrée. Taking this step will migrate all of the meat and fish recipes and change the type to Entrée.

The second part of this migration is far more complex. For each RecipeIngredient, we want to either create a new Ingredient entity or link the destination RecipeIngredient to an existing Ingredient. In addition, for each Ingredient that we create, we want to create a UnitOfMeasure entity to go with it.

To complete this very complex migration, we must resort to code. The mapping model editor isn’t capable of handling the complexity we need for this next bit of the migration. Fortunately, we can step in and write the code to handle the migration directly. To set this up, we start off by modifying the mapping model. The first step is to delete the mappings for Ingredient and UnitOfMeasure. There’s no corresponding entity in the source model, so no mapping is needed.

Next, we have to modify the RecipeIngredientToRecipeIngredient. After selecting the RecipeIngredientToRecipeIngredient mapping, we need to look at the Mapping Model inspector and set a custom policy. The Custom Policy field tells the migration that instead of using its built-in migration policy (which will read from the mapping model), we’ll introduce custom code. This field accepts a class name that we’re going to set to RecipeIngredientToIngredientAndUnitOfMeasure.

NSEntityMigrationPolicy

Core Data will instantiate an instance of NSEntityMigrationPolicy for each mapping in the mapping model. NSEntityMigrationPolicy is designed to be subclasses so that we can override all or part of the migration. There are several methods that can be overridden in the subclass; in this example, we’re overriding two methods.

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject*)src

entityMapping:(NSEntityMapping*)map

manager:(NSMigrationManager*)mgr

error:(NSError**)error

- (BOOL)createRelationshipsForDestinationInstance:(NSManagedObject*)dest

entityMapping:(NSEntityMapping*)map

manager:(NSMigrationManager*)mgr

error:(NSError**)error

Let’s take a closer look at each of these.

createDestinationInstancesForSourceInstance:

The first method, createDestinationInstancesForSourceInstance:, is called for each entity in the source store that’s associated with this migration policy. For example, during the migration of the RecipeIngredient entities and the creation of the Ingredient and UnitOfMeasure entities, this method would be called for each RecipeIngredient, and it’d be expected that at least an Ingredient entity would be created or associated with the incoming RecipeIngredient as a result.

The code to implement this breaks down as follows:

RecipesV3/PPRecipes/RecipeIngredientToIngredient.m

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject*)src

entityMapping:(NSEntityMapping*)map

manager:(NSMigrationManager*)mgr

error:(NSError**)error

{

NSManagedObjectContext *destMOC = [mgr destinationContext];

NSString *deName = [map destinationEntityName];

NSString *name = [src valueForKey:@"name"];

In the first part of the method, we’re simply setting up references that will be needed later. Specifically, we’re getting a reference to the destination NSManagedObjectContext, which we’ll need to create new entities, the name of the destination entity, and, most importantly, the name value from the entity.

Since the incoming entity is a RecipeIngredient, the name value will be the name of the ingredient that we now want to reference.

RecipesV3/PPRecipes/RecipeIngredientToIngredient.m

NSMutableDictionary *ui = (NSMutableDictionary*)[mgr userInfo];

if (!ui) {

ui = [NSMutableDictionary dictionary];

[mgr setUserInfo:ui];

}

NSMutableDictionary *inLookup = [ui valueForKey:@"ingredients"];

if (!inLookup) {

inLookup = [NSMutableDictionary dictionary];

[ui setValue:inLookup forKey:@"ingredients"];

}

NSMutableDictionary *uofmLookup = [ui valueForKey:@"unitOfMeasure"];

if (!uofmLookup) {

uofmLookup = [NSMutableDictionary dictionary];

[ui setValue:uofmLookup forKey:@"unitOfMeasure"];

}

In this next section of code, we deal with the possibility that the Ingredient entity that we need to reference has already been created. Rather than doing a fetch against the destination context every time, we have a hash built up and stored within the NSMigrationManager. The NSMigrationManager has an NSDictionary called userInfo that’s perfectly suited for this purpose. We first lazily initialize this dictionary, and then we lazily initialize another NSDictionary inside it to store references to the Ingredient entities using the name of the ingredient as the key. With this, we can make sure that each Ingredient is created only once.

For each Ingredient, we need to create or reference a UnitOfMeasure. We also grab a reference to the UnitOfMeasure lookup or create it if it hasn’t been created yet.

RecipesV3/PPRecipes/RecipeIngredientToIngredient.m

NSManagedObject *dest = [ingredientLookup valueForKey:name];

if (!dest) {

dest = [NSEntityDescription insertNewObjectForEntityForName:deName

inManagedObjectContext:destMOC];

[dest setValue:name forKey:@"name"];

[inLookup setValue:dest forKey:name];

name = [source valueForKey:@"unitOfMeasure"];

NSManagedObject *uofm = [uofmLookup valueForKey:name];

if (!uofm) {

id entityName = @"UnitOfMeasure";

uofm = [NSEntityDescription insertNewObjectForEntityForName:entityName

inManagedObjectContext:destMOC];

[uofm setValue:name forKey:@"name"];

[dest setValue:uofm forKey:@"unitOfMeasure"];

[uofmLookup setValue:uofm forKey:name];

}

}

Next we attempt to locate the Ingredient in the lookup dictionary. If it isn’t in the dictionary, we must create it and place it in the dictionary. If we need to create the Ingredient, we must resolve the UnitOfMeasure as well. Again, if it does not exist, we create it and put a reference to it in the lookup dictionary.

RecipesV3/PPRecipes/RecipeIngredientToIngredient.m

[manager associateSourceInstance:source

withDestinationInstance:dest

forEntityMapping:mapping];

return YES;

}

The last thing that we need to do is to tell the manager about the association. Since the manager keeps track of all associations between the two NSManagedObjectContext objects, we need to inform it of this new entity that was just created and that it’s associated with the source entity that was passed in. Once that’s complete, we return YES, and we’re done.

createRelationshipsForDestinationInstance:

In a properly designed data model, this method will rarely, if ever, be needed. The intention of this method (which is called in the second pass) is to build any relationships for the new destination entity that was created in the previous method. However, if all the relationships in the model are double-sided, this method isn’t necessary because we already set up one side of them. If for some reason there’s an entity in the model that is not double-sided, additional code would be required in this method to handle the one-sided relationship. Since we don’t need that functionality in our model, we just return YES.

RecipesV3/PPRecipes/RecipeIngredientToIngredient.m

- (BOOL)createRelationshipsForDestinationInstance:(NSManagedObject*)dIn

entityMapping:(NSEntityMapping*)map

manager:(NSMigrationManager*)mgr

error:(NSError**)error

{

return YES;

}

If you’re thinking this is a lot of work for migration, well, you’re right. Manual migrations require a great deal of effort on the part of the developer, and there isn’t a lot of benefit to doing one. This, plus their poor performance, is the reason for my recommendation to avoid them at nearly any cost. However, no matter how hard we try to avoid it, sometimes heavy migration is the only answer. Fortunately, as you just saw, the option is available.

Fundamentals of Core Data Versioning

We’ve seen the nuts and bolts, but what’s the magic behind all of this? How does the data migration actually work? As we already explored in Chapter 2, Under the Hood of Core Data, Core Data works with MOM (NSManagedObjectModel) objects that describe the data entities, their attributes, and their relationships. Core Data versioning works with those same MOM objects but takes the design one step further. Each entity version in each data model has a unique hash. When Core Data loads a persistent store from disk, it resolves the matching hashes in the persistent store against the MOM objects included with the application. If the matching MOM isn’t flagged as the “current” MOM, data migration kicks in.

How Data Migration Works

Core Data handles data migration in a very straightforward manner. When a persistent store needs to be migrated, Core Data performs three steps.

Copying the Entities with Attributes

In the first pass of the migration, Core Data creates new entities in the new persistent store for every entity in the old store. These entities have their attributes copied over, but not their relationships. During this phase, Core Data also keeps a reference to the old unique ID for each entity to be used in phase 2.

Creating Relationships Between the Entities

In the second pass, Core Data builds all the relationships between the entities based on the previous relationships. This is where the reference in phase 1 is used.

Validating the New Store

During the migration, all validation rules are turned off, and Core Data ignores the child classes defined in the MOM. Therefore, it’s possible that some data validation rules may have been broken during the migration. In the final phase of the migration, Core Data goes back through the store and checks all the validation rules in order to ensure the data is in a valid state.

Model Versions and Hashes

The word versioning has been used throughout this chapter as well as other material to describe data migration in Core Data. Unfortunately, it’s an inaccurate term. Versioning implies that there’s an order or precedence to the models. This isn’t accurate when it comes to data model versioning/migration in Core Data.

Entity Hashes

Instead of keeping track of a version number, creation date, or some other potentially chronological identifier, Core Data generates a hash for each entity in a model. The hashes are then stored within the persistent stores created with that model for later comparison. When a persistent store is loaded, the first thing Core Data does is retrieve the metadata from that store. Inside the metadata is a list of every entity type in the store, along with the hash for that entity. Core Data then compares that list of hashes against the hashes of the “current” MOM. If they match, everything is fine, and the store is loaded. If they don’t match, Core Data checks the options on the load persistent store call to see whether automatic data migration is requested. If it isn’t, an error message (shown in Some Maintenance Before We Migrate) is presented to the user.

Changing the Hash Values

Surprisingly, not everything that changes inside a MOM causes the hash of the entities inside to change. There are actually quite a few things that we can do to a model that don’t trigger data migration at all.

Changes That Alter the Entity Hash

If any of the following are changed on an entity, the entity will report a different hash.

- Name: Changing the name of the entity

- Inheritance: Changing the parent entity

- Persistent properties: Adding or removing a property

In addition, changing the following four properties will also trigger a change to the entity hash.

- Name: The name of the property

- Optionality/read-only: Whether the property is optional or read-only

- Attribute type: The type of value stored

- Relationship: The destination, the minimum/maximum count, the delete rule, or the inverse

Changes That Do Not Alter the Entity Hash

The following changes to an entity will not trigger a change to the entity hash:

- Class name: Changing the NSManagedObject subclass

- Transient properties: Changing properties that are not saved in the persistent store

- User info: Adding, removing, or changing the user info keys/values

- Validation predicates: Adding, removing, or changing the validation rules

- Default values: Adding, removing, or changing the default value of an attribute

In addition, the following changes to the properties of an entity will also not change the hash of the entity:

- User info: Adding, removing, or changing the user info key/values

- Validation predicates: Adding, removing, or changing the validation rules

The general distinction between things that do and do not affect version hashes is whether the changes impact the store schema. Details such as the class name impact only the runtime, not the structure of the persistent data.

Mapping Models

If Core Data detects that an upgrade to the persistent store is needed, it looks for these three files in the application bundle:

- The MOM that matches the hash from the persistent store

- The current MOM

- The mapping model for those two MOM objects

Assuming that all three files are located, Core Data will then migrate the data in the persistent store from the old model to the current model. If Core Data cannot locate a mapping model, it checks to see whether inferred (automatic) migration is turned on and attempts to do a lightweight migration. If inferred is not turned on or if the migration is too complex for a lightweight migration, an error will occur. Otherwise, once the migration is complete, the stack (MOC, PS, and MOM) is fully initialized, and the application continues. This, of course, is the happy path, and there are several safeguards in place to allow the application developer to control failures.

It’s vitally important that the application developer test the migration as completely as possible and that every error condition be checked during development and testing. With the delay in application releases to the App Store, it has never been more important to ensure that the migration performs correctly every time.

Progressive Data Migration (An Academic Exercise)

We’re going to wrap up this chapter with a final section that’s primarily a mental and academic exercise, one that demonstrates the flexibility available when working with Core Data migrations. Since it’s possible to insert custom code at nearly every point in a migration, we can do some very interesting things. Let’s take a look at an example.

What happens when our application is at version 5 of its data model and a user at version 1 decides to upgrade? Normally, we’d need to provide a mapping model for every combination of source and destination object models. For the first couple of versions, this isn’t an issue. However, as we get further away from version 1, it becomes increasingly difficult. Fortunately, it’s possible to figure out a migration path and do a progressive migration.

To accomplish a progressive migration, we need to handle the migration manually. The workflow is as follows:

1. If the store’s model is the current model, do nothing.

2. Find a mapping model with the current store’s model as its source.

3. Migrate the data to that mapping model’s destination model.

4. Repeat starting at step 1.

Creating the Migration Method

To begin this monumental task, we’ll create a new method in the AppDelegate. The method requires several pieces of information: the source path, the source type (XML, SQL, and so on), and the final model. In addition, we’ll pass in an error to be able to report any failures.

RecipesV3/PPRecipes/PPRAppDelegate.m

- (BOOL)progressivelyMigrateURL:(NSURL*)sourceStoreURL

ofType:(NSString*)type

toModel:(NSManagedObjectModel*)finalModel

error:(NSError**)error

{

It’s a rather unwieldy method name, to be sure, but it contains all the information we need to figure out our migration path. Since this is going to be a recursive method, the first thing we need to do is check to see whether we’re at our goal.

RecipesV3/PPRecipes/PPRAppDelegate.m

NSDictionary *sourceMetadata =

[NSPersistentStoreCoordinator metadataForPersistentStoreOfType:type

URL:sourceStoreURL

error:error];

if (!sourceMetadata) return NO;

if ([finalModel isConfiguration:nil

compatibleWithStoreMetadata:sourceMetadata]) {

*error = nil;

return YES;

}

In this code segment, we first retrieve the metadata from the source URL. If that metadata is not nil, we ask the final model whether the metadata is compatible with it. If it is, we are happy and done. We then set the error pointer to nil and return YES. If it isn’t compatible, we must try to figure out the mapping model and potentially the interim data model to migrate to.

Finding All the Managed Object Models

To proceed to the next step in the migration, we need to find all the managed object models in the bundle and loop through them. The goal at this point is to get all the models and figure out which one we can migrate to. Since these models will probably be in their own bundles, we have to first look for the bundles and then look inside each of them.

//Find the source model

NSManagedObjectModel *sourceModel = [NSManagedObjectModel

mergedModelFromBundles:nil

forStoreMetadata:sourceMetadata];

NSAssert(sourceModel != nil, ([NSString stringWithFormat:

@"Failed to find source model\n%@",

sourceMetadata]));

//Find all of the mom and momd files in the Resources directory

NSMutableArray *modelPaths = [NSMutableArray array];

NSArray *momdArray = [[NSBundle mainBundle] pathsForResourcesOfType:@"momd"

inDirectory:nil];

for (NSString *momdPath in momdArray) {

NSString *resourceSubpath = [momdPath lastPathComponent];

NSArray *array = [[NSBundle mainBundle]

pathsForResourcesOfType:@"mom"

inDirectory:resourceSubpath];

[modelPaths addObjectsFromArray:array];

}

NSArray* otherModels = [[NSBundle mainBundle] pathsForResourcesOfType:@"mom"

inDirectory:nil];

[modelPaths addObjectsFromArray:otherModels];

if (!modelPaths || ![modelPaths count]) {

//Throw an error if there are no models

NSMutableDictionary *dict = [NSMutableDictionary dictionary];

[dict setValue:@"No models found in bundle"

forKey:NSLocalizedDescriptionKey];

//Populate the error

*error = [NSError errorWithDomain:@"Zarra" code:8001 userInfo:dict];

return NO;

}

In this code block, we first grab all the resource paths from the mainBundle that are of type momd. This gives us a list of all the model bundles. We then loop through the list and look for mom resources inside each and add them to an overall array. Once that’s done, we look inside the mainBundle again for any freestanding mom resources. Finally, we do a failure check to make sure we have some models to look through. If we can’t find any, we populate the NSError and return NO.

Finding the Mapping Model

Now the complicated part comes in. Since it isn’t currently possible to get an NSMappingModel with just the source model and then determine the destination model, we have to instead loop through every model we find, instantiate it, plug it in as a possible destination, and see whether there’s a mapping model in existence. If there isn’t, we continue to the next one.

RecipesV3/PPRecipes/PPRAppDelegate.m

NSMappingModel *mappingModel = nil;

NSManagedObjectModel *targetModel = nil;

NSString *modelPath = nil;

for (modelPath in modelPaths) {

targetModel = [[NSManagedObjectModel alloc]

initWithContentsOfURL:[NSURL fileURLWithPath:modelPath]];

mappingModel = [NSMappingModel mappingModelFromBundles:nil

forSourceModel:sourceModel

destinationModel:targetModel];

//If we found a mapping model then proceed

if (mappingModel) break;

//Release the target model and keep looking

[targetModel release], targetModel = nil;

}

//We have tested every model, if nil here we failed

if (!mappingModel) {

NSMutableDictionary *dict = [NSMutableDictionary dictionary];

[dict setValue:@"No models found in bundle"

forKey:NSLocalizedDescriptionKey];

*error = [NSError errorWithDomain:@"Zarra"

code:8001

userInfo:dict];

return NO;

}

This section is probably the most complicated piece of the progressive migration routine. In this section, we’re looping through all the models that were previously discovered. For each of those models, we’re instantiating the model and then asking NSMappingModel for an instance that will map between our known source model and the current model. If we find a mapping model, we break from our loop and continue. Otherwise, we release the instantiated model and continue the loop. After the loop, if the mapping model is still nil, we generate an error stating that we cannot discover the progression between the source model and the target and return NO. At this point, we should have all the components we need for one migration. The source model, target model, and mapping model are all known quantities. Now it’s time to migrate!

Performing the Migration

In this block, we’re instantiating an NSMigrationManager (if we needed something special, we’d build our own manager) with the source model and the destination model. We’re also building up a unique path to migrate to. In this example, we’re using the destination model’s filename as the unique change to the source store’s path. Once the destination path is built, we then tell the migration manager to perform the migration and check to see whether it was successful. If it wasn’t, we simply return NO because the NSError will be populated by the NSMigrationManager. If it’s successful, there are only three things left to do: move the source out of the way, replace it with the new destination store, and finally recurse.

RecipesV3/PPRecipes/PPRAppDelegate.m

NSMigrationManager *manager = [[NSMigrationManager alloc]

initWithSourceModel:sourceModel

destinationModel:targetModel];

NSString *modelName = [[modelPath lastPathComponent]

stringByDeletingPathExtension];

NSString *storeExtension = [[sourceStoreURL path] pathExtension];

NSString *storePath = [[sourceStoreURL path] stringByDeletingPathExtension];

//Build a path to write the new store

storePath = [NSString stringWithFormat:@"%@.%@.%@", storePath,

modelName, storeExtension];

NSURL *destinationStoreURL = [NSURL fileURLWithPath:storePath];

if (![manager migrateStoreFromURL:sourceStoreURL

type:type

options:nil

withMappingModel:mappingModel

toDestinationURL:destinationStoreURL

destinationType:type

destinationOptions:nil

error:error]) {

return NO;

}

In this final code block, we first create a permanent location for the original store to be moved to. In this case, we’ll use a globally unique string generated from the NSProcessInfo class and attach the destination model’s filename and the store’s extension to it. Once that path is built, we move the source to it and then replace the source with the destination. At this point, we’re at the same spot we were when we began except that we’re now one version closer to the current model version.

Now we need to loop back to step 1 again in our workflow. Therefore, we’ll recursively call ourselves, returning the result of that recurse. As you can recall from the beginning of this method, if we’re now at the current version, we’ll simply return YES, which will end the recursion.

RecipesV3/PPRecipes/PPRAppDelegate.m

NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString];

guid = [guid stringByAppendingPathExtension:modelName];

guid = [guid stringByAppendingPathExtension:storeExtension];

NSString *appSupportPath = [storePath stringByDeletingLastPathComponent];

NSString *backupPath = [appSupportPath stringByAppendingPathComponent:guid];

NSFileManager *fileManager = [NSFileManager defaultManager];

if (![fileManager moveItemAtPath:[sourceStoreURL path]

toPath:backupPath

error:error]) {

//Failed to copy the file

return NO;

}

//Move the destination to the source path

if (![fileManager moveItemAtPath:storePath

toPath:[sourceStoreURL path]

error:error]) {

//Try to back out the source move first, no point in checking it for errors

[fileManager moveItemAtPath:backupPath

toPath:[sourceStoreURL path]

error:nil];

return NO;

}

//We may not be at the "current" model yet, so recurse

return [self progressivelyMigrateURL:sourceStoreURL

ofType:type

toModel:finalModel

error:error];

This progressive migration can be tested by first running version 1 of our application, entering some data, and then running the third version. You’ll then see the data model migrate seamlessly from version 1 to version 3 with no intervention required.

Wrapping Up

We explored how to deal with changes and additions to our application and discussed data migration. In the next chapter, we’re going to take a look at how our applications perform and ways to tune them.

Copyright © 2016, The Pragmatic Bookshelf.

Chapter 5

Performance Tuning

Brent Simmons, creator of NetNewsWire, once shared a story about a user who filed a bug report about the poor start-up performance of NetNewsWire. Upon discussion with that user, he discovered that they had more than 900,000 unread RSS feeds! The lesson I took away from that story is to expect my users to put thousands of times as much data into my applications as I would ever consider reasonable.

While we’re working with Core Data, we need to consider the performance impacts of our design. We might test with a couple of dozen recipes and expect our users to load a couple hundred recipes into our application and test with those expectations. However, our users can’t read our intentions or expectations. As soon as we ship the application, some user somewhere will load 100,000 recipes into it and then file a bug report that it performs poorly.

Persistent Store Types

Four types of repositories are included with the Core Data API: SQLite, XML, binary, and in-memory. (XML is available only on OS X, not on iOS.) In-memory is technically not a persistent store because it is never written out to disk. Binary is effectively a serialized version of the object graph written out to disk. The XML store writes out the object graph to a human-readable text file, and SQLite stores the object graph in a relational database. When you’re working with an iOS project, it’s common to just use SQLite unless there’s a specific reason to use one of the other store formats.

Atomic Stores

Atomic stores include XML, binary, and custom data stores. All of these stores are written to disk atomically; in other words, the entire data file is rewritten on every save. Although these store types have their advantages, they don’t scale as well as the SQLite store. In addition, they’re loaded fully into memory when they’re accessed. This causes atomic stores to have a larger memory footprint than a SQLite store.

However, because they reside completely in memory while the application is running, atomic stores can be very fast, since the disk is hit only when the file is read into memory and when it’s saved back out. SQLite, although still considered a fast store, is slower when dealing with smaller data sets because of its inherent disk access. The differences are measured in fractions of a second, so we cannot expect a dramatic speed increase by using an atomic store. But if fractions of a second matter, it may be something to consider.

SQLite Persistent Store

In the most common cases, SQLite is the store option to use for application development. This is true on both iOS and OS X. SQLite is a software library that implements a self-contained, server-less, zero-configuration, transactional SQL database engine. SQLite is the most widely deployed SQL database engine in the world. The source code for SQLite is in the public domain.

Better Scaling

By utilizing a relational database as the persistent store, we no longer need to load the entire data set into memory to work with it. Because the data is being stored in a relational database, our application can scale to a very large size. SQLite itself has been tested with data sets measured in terabytes and can handle just about anything that we can realistically develop. Since we’re loading only the data we want at a particular moment, SQLite keeps the memory footprint of our application quite low. Likewise, SQLite makes efficient use of its disk space and therefore has a small footprint on disk as well.

More Performance-Tuning Options

By working with a database instead of a flat file, we have access to many more performance-tuning options. For example, we can index the columns within our entities to enable faster predicates. We can also control exactly what gets loaded into memory. It’s possible to get just a count of the objects, just the unique identifiers for objects, and so on. This flexibility allows us to tune the performance of our application more than any other store type. Because the SQLite store is the only format that’s not fully loaded into memory, we get to control the data flow. All of the other formats require that the entire data file be loaded into memory before they can be used. The details of how to utilize these features are discussed in Fetching.

Optimizing Your Data Model

When we design our data model, we should consider several factors. Where we put our binary data can be extremely important because its size and storage location plays a key role in the performance of our application. Likewise, relationships must be carefully balanced and used appropriately. Also, entity inheritance, a powerful feature of Core Data, must be used with a delicate hand because the underlying structure may be surprising.

Although it’s easy to think of Core Data as a database API, we must remember that it’s not and that structuring the data with data normalization may not yield the most efficient results. In many cases, denormalizing the data can yield greater performance gains.

Where to Put Binary Data

One of the easiest ways to kill performance in a Core Data application is to stick large amounts of binary data into frequently accessed tables. For example, if we were to put the pictures of our recipes into the recipe table, we’d start seeing performance degradation after only a couple hundred recipes had been added. Every time we accessed a Recipe entity, we’d have to load its image data, even if we weren’t going to display the image. Since our application displays all the recipes in a list, this means every image would reside in memory immediately upon launch and remain there until the application quit. Imagine this situation with a few thousand recipes!

But where do we draw the line? What’s considered a small enough piece of binary data to fit into a table, and what shouldn’t be put into the repository at all?

If you’re developing an application that’s targeting iOS 6.0 or greater (or Mac OS X 10.8 or greater), the answer is simple: turn on external binary storage in the model and let Core Data solve the problem for you. Just set the external record flag in the Data Model inspector panel, as shown here.

This feature instructs Core Data to determine how to store binary data. With this flag on, Core Data decides whether the image is small enough to store inside the SQLite file or whether it’s too big and therefore should be stored on disk separately. In either case, the decision is an implementation detail from the perspective of our application. We access the binary data just like any other attribute on the entity.

If the application is still targeting an older version of the operating system (iOS or Mac OS X), then the application is responsible for dealing with binary data in a performant way.

Small Binary Data

Anything smaller than 100 kilobytes is considered to be small binary data. Icons or small avatars are a couple examples of data of this size. When you’re working with something this small, it’s most efficient to store it directly as a property value in its corresponding table. The performance impact of binary data this size is negligible. The transformable attribute type is ideal for this situation.

Medium Binary Data

Medium binary data is anything larger than 100 kilobytes and smaller than 1 megabyte in size. Average-sized images and small audio clips are a few examples of data in this size range. Data of this size can also be stored directly in the repository. However, the data should be stored in its own table on the other end of a relationship with the primary tables. This allows the binary data to remain a fault until it’s actually needed. In the previous recipe example, even though the Recipe entity would be loaded into memory for display, the image would be loaded only when it’s needed by the UI.

SQLite has shown itself to be quite efficient at disk access. There are cases where loading data from the SQLite store can actually be faster than direct disk access. This is one of the reasons why medium binary data can be stored directly in the repository.

Large Binary Data

Large binary data is anything greater than 1 megabyte in size. Large images, audio files, and video files are just some examples of data of this size. Any binary data of this size should be stored on disk as opposed to in the repository. When you’re working with data of this size, it’s best to store its path information directly in the primary entity (such as the Recipe entity) and store the binary data in a known location on disk (such as in the Application Support subdirectory for your application).

Entity Inheritance

Entity inheritance is a powerful feature within Core Data. It allows you to build an object-like inheritance tree in your data model. However, this feature comes at a rather large cost. For example, let’s look at an example model that makes moderate use of entity inheritance, as shown here:

The object model itself looks quite reasonable. We’re sharing name, desc, and a one-to-many relationship to the ImageEntity. However, the underlying table structure actually looks like this:

The reason for this is how Core Data handles the object model--to--relational table mapping. Instead of creating one table for each child object, Core Data creates one large table that includes all the properties for the parent entity as well as its children. The end result is an extremely wide and tall table in the database with a high percentage of empty values.

Although the entity inheritance feature of Core Data is extremely useful, we should be aware of what’s going on underneath the object model to avoid a performance penalty. We shouldn’t treat entity inheritance as an equal to object inheritance. There’s certainly some overlap, but they aren’t equal, and treating them as such will have a negative impact on the performance of the repository.

Denormalizing Data to Improve Performance

Although the most powerful persistent store available for Core Data is a database, we must always be conscious of the fact that Core Data isn’t just a database. Core Data is an object hierarchy that can be persisted to a database format. The difference is subtle but important. Core Data is first a collection of objects that we use to display data in a user interface of some form and allow the user to access that data. Therefore, although database normalization might be the first place to look for performance improvements, we shouldn’t take it too far. There are six levels of database normalization,[4] but a Core Data repository should rarely, if ever, be taken beyond the second level. In several cases, we can gain a greater performance benefit by denormalizing the data.

Search-Only Properties

Searching within properties can be quite expensive. For properties that have a large amount of text or, worse, Unicode text, a single search field can cause a huge performance hit. One way to improve this situation is to create a derived attribute based on the text in an entity. For example, searching in our description property of the Recipe entity can potentially be very expensive if the user has verbose descriptions and/or uses Unicode characters in the description.

To improve the performance of searches in this field, we could create a second property on the Recipe entity that strips the Unicode characters from the description and also removes common words such as a, the, and, and so on. If we then perform the search on this derived property, we can drastically improve search performance.

The downside to using search-only properties is that we need to maintain them. Every time the description field is edited, we have to update the derived property as well.

Expensive Calculations

In a normalized database, calculated values aren’t stored. It’s considered cheaper to recalculate the value as needed than to store it in the database. However, from a user experience point of view, the opposite can frequently be true. In cases where the calculation takes a human-noticeable amount of time, it may very well be better for the user if we were to store that calculation in the entity and recalculate it only when one of its dependent values has changed. For example, if we store the first and last names of a user in our Core Data repository, it might make sense to store the full name as well.

Intelligent Relationships

Relationships in a Core Data model are like salt in a cooking recipe. Too much and you ruin the recipe; too little and something is missing. Fortunately, there are some simple rules we can follow when it comes to relationships in a Core Data repository.

Follow the Object Model

Core Data is first and foremost an object model. The entities in our model should represent the data as accurately as possible. Just because a value might be duplicated across several objects (or rows from the database point of view) does not mean it should be extruded into its own table. Many times it’s more efficient for us to store that string several times over in the entity itself than to traverse a relationship to get it.

Traversing a relationship is generally more expensive than accessing an attribute on the entity. Therefore, if the value being stored is simple, it’s better to leave it in the entity it’s associated with.

Separate Commonly Used from Rarely Used Data

If the object design calls for a one-to-many relationship or a many-to-many relationship, we should definitely create a relationship for it. This is usually the case where the data is more than a single property or contains binary data or would be difficult to properly model inside the parent object. For example, if we have a user entity, it’s more efficient to store the user’s address in its own object as opposed to having several attributes in the user object for address, city, state, postal code, and so on.

A balance needs to be carefully maintained between what’s stored on the other end of a relationship and what’s stored in the primary entity. Crossing key paths is more expensive than accessing attributes, but creating objects that are very wide also slows down data access.

Fetching

Fetching is the term used to describe the resolving of NSManagedObject objects from the repository. When we retrieve an NSManagedObject, it is “fetched” into memory, and we can then access its properties. To help us utilize memory efficiently, fetching may not always happen all at once. Specifically, when we’re using a SQLite store, it’s quite possible that an object we think is in memory is actually only on disk and has yet to be read into memory. Likewise, objects that we think we’re done with may actually still sit in a cache.

To demonstrate the differences in the ways that we can load data into memory from our SQLite Store, I ran a number of tests using one of Apple’s older demonstration application from a previous Worldwide Developer Conference (WWDC 2008) called GoFetch. The entire goal of this application is to generate a large amount of random data and let us control how it’s fetched back into memory. Each fetch is then timed to demonstrate the speed of various options. These tests were performed with 3,417 records in the SQLite repository.

Loading NSManagedObjectID Objects Only

The smallest amount of data that we can retrieve as part of an NSFetch-Request is just the NSManagedObjectID. The NSManagedObjectID is the unique identifier for the record and contains no content. In the test discussed earlier, it took the test machine 0.004 seconds to retrieve 3,417 records from disk.

There’s only one change required to retrieve just NSManagedObjectID objects instead of full NSManagedObject objects.

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

[fetchRequest setEntity:[NSEntityDescription entityForName:@"Person"

inManagedObjectContext:[self managedObjectContext]]];

[fetchRequest setResultType:NSManagedObjectIDResultType];

By changing the ‑resultType to NSManagedObjectIDResultType, our call to ‑executeFetchRequest:error: returns NSArray of NSManagedObjectID instead of NSManagedObject objects.

Loaded As a Fault

The next smallest amount of data we can retrieve is referred to as a faulted NSManagedObject. What this means is the NSFetchRequest returns an NSArray of NSManagedObject objects, but those objects contain only the NSManagedObjectID. All the properties and relationships are empty or in a faulted state. As soon as an attribute is accessed, all of the attributes on that object are loaded in. Likewise, as soon as a relationship is accessed, all the NSManagedObject objects on the other end of that relationship are loaded in as faults. Performing the same query as earlier in this configuration returned the 3,417 records in 0.007 seconds. Faults will be discussed further in Faulting.

To disable the fetching of attributes as part of the NSFetchRequest, we need to configure the fetch prior to executing it.

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

[fetchRequest setIncludesPropertyValues:NO];

[fetchRequest setEntity:[NSEntityDescription entityForName:@"Person"

inManagedObjectContext:[self managedObjectContext]]];

Although this seems like a great solution, it can be a bit of a trap. Because this configuration returns empty skeletons, each object gets loaded from disk individually. This is significantly slower than loading all the objects needed at once. However, the time to load the objects is spread out and can be less noticeable to the user. For raw speed, it’s recommended that we load all the data for the objects in one pass.

Loading Property Values

The next step up from faulted NSManagedObject objects is to prefetch their property values. This won’t retrieve the objects on the other sides of relationships. Performing this query took 0.021 seconds for the 3,417 records in the test repository.

How to Retrieve Only Property Values

Retrieving NSManagedObject objects with attributes populated is the default for NSFetchRequest.

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

[fetchRequest setEntity:[NSEntityDescription entityForName:@"Person"

inManagedObjectContext:[self managedObjectContext]]];

This option is a very good middle ground between fetching faults and some of the following choices. In situations where only the object requested needs to be displayed right away and its relationships aren’t required right away, this can be the most efficient solution.

How to Load Property Values and NSManagedObjectID Objects

We can also combine this option with the NSManagedObjectID retrieval listed earlier to warm up the cache. The settings to accomplish this are as follows:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

[fetchRequest setResultType:NSManagedObjectIDResultType];

[fetchRequest setEntity:[NSEntityDescription entityForName:@"Person"

inManagedObjectContext:[self managedObjectContext]]];

This can be used to excellent effect on a background thread when the entire fetch is going to take a significant amount of time. Once the NSManagedObjectID objects are retrieved, they can be safely passed to the primary thread and used to display the data to the user. Using Core Data within a multithreaded application is discussed in more detail in Chapter 6, Threading.

Loading Relationships

The next step up in the scale of loading data is to prefetch the relationships while loading the targeted entities. This doesn’t fetch them as fully formed but as faults. This step up can have a significant impact on the performance of a Core Data application. In the test, this fetch took 1.166 seconds to retrieve 3,417 objects, each with only a single object on the other side of a one-to-one relationship. With a more complex data model, this becomes an even larger performance hit.

Fortunately, this option gives us some fine-grained control over which relationships to load. This would allow us to, for example, load only the addresses associated with a person and skip over their images, phone numbers, and so on. Accomplishing this requires passing an NSArray of NSString objects with the names of the relationships to load.

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

NSArray *relationshipKeys = [NSArray arrayWithObject:@"addresses"];

[fetchRequest setRelationshipKeyPathsForPrefetching:relationshipKeys];

[fetchRequest setEntity:[NSEntityDescription entityForName:@"Person"

inManagedObjectContext:[self managedObjectContext]]];

In this example code, we create a new NSArray that has one NSString within it that corresponds to the name of the relationship within the Person entity. We can get even more clever with this request by using a keypath in the NSArray and specifying a second level of objects to include in the fetch. For example, if our Address entities had a relationship to a postal code lookup table that contained the city and state, we could change the NSArray creation line to the following:

NSArray *relationshipKeys = [NSArray arrayWithObjects:@"addresses",

@"addresses.postalCode", nil];

That would cause Core Data to retrieve two levels of relationships as faults. In addition, this call does check for duplication before executing the requests and thereby can be used safely when mixing keypaths. In other words, the postalCode relationship, which is probably many to many, will not be retrieved more than once.

NSFetchRequest and Disk Access

Every time an NSFetchRequest is executed, it hits the disk. This is an important point to keep in mind when we’re working with NSManaged-Object objects. If we’re doing joins, adding objects to a relationship, and so on, it might seem easier and cleaner to perform an NSFetchRequest to check whether the object is already in the relationship or check for a similar function, but that action can hurt performance significantly. Even if we have all the relevant objects in memory, an NSFetchRequest is still going to hit the disk. It’s far more efficient for us to use that NSPredicate against a collection that’s already in memory.

We have seen in this section that with a SQLite persistent store, we have a lot of control over how our data is loaded into memory. We can tailor the load to fit our exacting needs. All of these options can be a bit overwhelming, but there’s one good rule of thumb. Try to load only the data you need at that moment in one pass. Every fetch request can take quite a bit of time, and since the fetch requests are normally performed on the main thread, they can damage the user experience of your application.

Faulting

Firing faults individually is one of the most common, if not the most common, cause for the poor performance of Core Data applications. Faults are a double-edged sword that can make great improvements to the speed and performance of our applications or can drag the performance down to the depths of the unusable. The single most valuable performance improvement we can make to a Core Data application is to make sure we’re fetching only the data we need when we need it. If we fetch too little, our application will feel unresponsive. If we fetch too much, our application will stall and potentially be killed by the operating system.

Orders of Magnitude

Disk access is significantly slower than accessing memory. The times measured for each is six orders of magnitude different. This translates into disk access being roughly 1 million times slower than accessing data that’s stored in memory, as illustrated:

Although the actual retrieval times are closer to a few thousand times slower, the point is still clear. Avoid accessing the disk if possible. However, when we have no choice but to access the disk, we must attempt to get everything we need in one pass. Repeated small requests to the disk are significantly slower than one larger request.

Prefetching

In Fetching, we reviewed the different ways we can retrieve the data from disk. To expand on that, consider each request we make from the NSManagedObjectContext and try to retrieve all the data in one request that the user is going to want to view. For example, if the user is going to be editing a user record, load that user and all its relationships at once. This will be significantly faster than grabbing the Person entity and then going back to grab three Address entities, then two Phone entities, and so on. Use the relationship prefetching option of NSFetchRequest to grab all of them at once.

If we can predict what users are going to want to see and load it ahead of their request, the overall user experience will be vastly improved. As we’re developing our applications, we need to look at each window, view, or sheet and ask, “What information will this part present?” and make sure all of that information is either already in memory or loaded at once. Having said that, we need to balance this with information overload, as discussed in Access Patterns.

Warming Up the Cache

It’s possible to preload the data into the cache so that it’s in memory when we need it. The easiest way to perform this step is to execute a full fetch on a background thread. For example, on launch of our recipe application, we could launch a background thread to retrieve all the Recipe entities. This would allow us to fill the cache with the Recipe entities that we know are going to be presented to the user, in turn allowing the main thread to grab those recipes from the cache instead of the disk and give the user a smoother-running application in the process. The magic behind this is based on how the NSPersistentStoreCoordinator works. Whenever any request on any thread is performed, the data is held in the NSPersistentStoreCoordinator as part of its cache. When another request is made, no matter what thread it came from, for that same data it’s retrieved from the cache instead of requiring another hit to the disk.

Saving

The numbers discussed in Orders of Magnitude, also apply to writing the data back out to the disk. In fact, writing to the disk is even slower than reading from it. Therefore, it’s more efficient for us to save data back out to disk in batches. Saving after every record change causes our entire application to feel sluggish to the user. Likewise, doing a huge write while the application is attempting to exit gives the appearance that our application has stopped responding and risks data loss.

As with most things, when it comes to performance tuning be aware of your saves and how much data you’re saving and how frequently. Try to do saves during logical pauses in the application flow.

Deleting

It may come as a surprise, but deleting an object can cause a performance issue. Let’s review the data model from Chapter 4, Versioning and Migration. Imagine that in this later version of our application we want to delete a recipe from the repository. When we delete the recipe, we have a cascade rule set up to delete all the associated RecipeIngredient entities as well. We also need to touch the Author entity, Ingredient entity, and UnitOfMeasure entity, as shown here.

It’s obvious why we need to touch the RecipeIngredient entity, but why do we need to access all the others? This is because of the relationships between the entities. For each relationship, we need to validate the relationship after the delete and confirm that there are no dangling references. If these objects aren’t currently in memory, then the NSManagedObjectContext must retrieve them from the disk to accomplish all of this.

Therefore, when we’re doing deletes, especially large deletes, it can be a performance improvement to fetch all the relationships prior to the delete.

Faulting and Disk Access

Firing a fault doesn’t always mean that the data is going to be read from disk. Depending on how we’ve requested the data in the first place or what happened earlier in the NSManagedObject object’s life span, it’s quite possible that the data will be loaded from cache instead.

Likewise, faulting an NSManagedObject doesn’t guarantee that it will be written back out to disk, nor does it guarantee that it will be removed from the cache. If the object has no changes, then there’s nothing to write to disk, and it’s quite possible it will remain in the cache for an unknown period of time.

Easily one of the best ways to check to see whether the firing of a fault is in fact causing disk access is to monitor our application with Instruments.By using the Core Data template, we can use the “cache miss” instrument to check for disk hits. If we’re getting far more calls to the disk than expected, we need to consider refactoring the code.

Access Patterns

Improving performance within Core Data isn’t necessarily only about the repository and order of loading the data. There are a number of things we can do within the user interface to help performance as well.

Searching the repository can be absolute murder on performance. Whether we’re searching at the request of the user or performing a search in the background, we need to be very careful to avoid impacting the performance of our application.

Order Is Important

Just like any conditional, the order of the logic is important. Simple equality is faster than inclusions such as in, contains, and so on. When building the predicate, try to order the logic from left to right, simple to complex. This process allows Core Data to fail quickly and improve the search performance.

Unicode and Regular Expressions

Unicode is very expensive to work with when we’re searching. As suggested earlier in Search-Only Properties, try to avoid searching against Unicode directly. It’s cheaper to keep a derived value that strips off the Unicode than it is to do frequent searches against the Unicode text.

Likewise, regular expressions are expensive. If a situation calls for one, try to put it at the far-right end of the NSPredicate, as discussed in the previous section.

Limit Queries Across Relationships

Searching across objects that are joined by a relationship can be very expensive. Although it’s impressive to search against person.address.postalCode.city, it may not be the most efficient way to solve the problem. Consider reversing the query or breaking it down into several smaller queries to reduce the complexity of the underlying SQL. When we’re working with a SQLite back end, all of our NSPredicate calls turn into SQL before hitting the database. The less complex that SQL is, the faster it will run. It may very well be faster to get an NSArray of all the Address objects within a specific city and then perform the rest of the query against that NSArray than it would be to traverse three relationships in one call.

Wrapping Up

A busy user interface is more than just a poor user experience; it also impacts the performance of the application. When we display a large amount of data on the screen, we must keep that information in memory, which in turn means we must load a large amount of data from disk all in one go. It’s far better to break up an application user interface into consumable chunks of information than it is to try to display every last bit on the screen at once.

The careful use of tabs, sheets, and panels can improve the feel of a user interface, and that will in turn improve the performance. By splitting the user interface into smaller pieces, we have finer-grained control over what data gets loaded when, and we can reduce our disk access to manageable chunks.

Keep it simple.

Footnotes

[4]

See http://en.wikipedia.org/wiki/Database_normalization for details.

Copyright © 2016, The Pragmatic Bookshelf.

Chapter 6

Threading

Multithreading is one of the great double-edged swords of programming. If it’s done correctly, it can be a real boon to your application; done incorrectly, it leads to strange, unreproducible errors in the application. Multithreading has a tendency to polarize developers: they either swear that it’s necessary for any application to perform properly or declare it’s to be avoided at all costs. The truth, of course, is somewhere in the middle. Multithreading is a vital piece of the overall performance puzzle. While adding more threads won’t make your application automatically faster, it can make it “feel” faster to the user. That perception is what we’re going to focus on in this chapter.

It’s a common misconception among developers that the point of adding threads to an application is to improve performance. While there’s no argument that proper threading support can improve performance in an application, treating threading like a silver bullet is a sure way to disaster.

Threading should be introduced to an application as part of the design process, whenever there’s a situation where the application can or should be doing more than one thing. Any situation during the application design where an operation is needed but the user doesn’t need to wait on that operation is a perfect situation for an additional thread.

Here are some common operations that fall into this category:

- Exporting data to a web service

- Importing data from a web service

- Recalculating data (totals and balances)

- Caching images

- Caching videos

And the list goes on. In addition to these concepts there’s the concept of anticipating the user and what data the user is going to want next. If, for example, you’re developing a news application, it makes sense to load the full news articles, images, videos, and so forth while the user is still scrolling through the list of articles. Ideally your application will have the data loaded and ready to display before the user selects it.

When an application can correctly predict what data a user is going to want to see before the user requests it, that application reaches a whole new level in user experience.

The purpose of adding threads to your application is to improve user experience by offloading work the user doesn’t need to be blocked by, as well as predictively load data before the user needs it.

With that goal for threading in mind, let’s look at how to use Core Data in a multithreaded environment.

Threading and Core Data

Throughout the life of the Core Data framework, the approach to using Core Data with threading has changed many times. Originally there was no support for threading other than “figure it out,” which evolved into the basic rule of “A context and its data must stay on one thread.”

With the introduction of iOS 5.0 and OS X 10.8, Core Data began utilizing GCD (Grand Central Dispatch) and blocks that were introduced to the overall system in the previous generation. To further define how threading should be approached with Core Data, the threading model was again refined in iOS 8.0 and OS X 10.10, and yet again in iOS 9.0 and OS X 10.11.

With these changes, the threading model for Core Data has become a binary decision. We can use Core Data on the main thread (also known as the UI thread), or we can use Core Data on a background thread for things that don’t directly impact the UI thread, which is a binary question. This evolution has been an incredibly good thing for Core Data. The question of how and when to use threads has been boiled down to a single Boolean question.

Starting with iOS 9.0 and OS X 10.11, the generic -init method of the NSManagedObjectContext has been deprecated. Previously, when you called this initializer an instance of NSManagedObjectContext was returned that was associated with the thread that created it.

This type of creation was one of the parts of Core Data and threading that confused developers and therefore was finally removed. Now, when you want a new instance of NSManagedObjectContext you need to explicitly specify what thread that the NSManagedObjectContext will be associated with:

NSManagedObjectContext *moc = nil;

moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:${XXX}];

With this initializer, we can pass in two options for ${XXX}:

- NSMainQueueConcurrencyType: This option will configure the NSManagedObjectContext so that it can only be run on the main queue/thread.

- NSPrivateQueueConcurrencyType: This option will configure the NSManagedObjectContext so that it can only be run on a private queue/thread.

As mentioned, this is now a binary decision when we’re initializing an NSManagedObjectContext. If the NSManagedObjectContext is going to be used with the user interface, then the NSMainQueueConcurrencyType will be used. Otherwise you must use the NSPrivateQueueConcurrencyType.

Working on the Main Queue

In general, working on the main queue hasn’t changed from the original design. Assuming you’re working with an NSManagedObjectContext that’s configured to run on the main queue, you’d access that NSManagedObjectContext the exact same way as before.

The big difference is when your code is on another queue and you need to do some data work on the main queue. Getting that work onto the main queue has changed, fortunately for the better. This improvement is in the form of two methods: -performBlock: and -performBlockAndWait:.

Introducing -performBlock:

The goal of -performBlock: is to guarantee that a block of code is being executed on the correct queue, which is the queue that the NSManagedObjectContext is associated with. Therefore, if you have a block of code that you need to execute on the main queue against the NSManagedObjectContext associated with the main queue, you can do the following:

NSManagedObjectContext *moc = ...; //Reference to main queue context

[moc performBlock:^{

NSFetchRequest *request = ...;

//... Define the request

NSError *error = nil;

NSArray *results = [moc executeRequest:request error:&error];

if (!results) {

NSLog(@"Failed to fetch: %@\n%@", [error localizedDescription],

[error userInfo]);

}

// Do something with the results

}];

In this example, you retrieve a reference to the existing NSManagedObjectContext that’s instantiated against the main queue. From there you call -performBlock: and inside that block is where you do all of the work that needs to be performed on the main queue against Core Data.

The call to -performBlock: takes the block of code and puts in the “todo” list for the queue associated with the NSManagedObjectContext that it’s called against. As soon as that queue gets to the block of code, it will be executed. Generally the execution happens right away, but if that queue is busy with another task (for example, it has another block of code to execute), then the block will be performed later.

Calling -performBlock: isn’t a “blocking” call, which means that the queue that calls -performBlock: won’t be halted or paused and the line of code after the -performBlock: call will be executed immediately—most likely before the block is executed.

What this also means is that the -performBlock: call is re-entrant. While you’re inside one call to -performBlock:, you can kick off another call. Your second call to -performBlock: is guaranteed to be executed after the first call. Therefore, you could do something clever like this:

NSManagedObjectContext *moc = ...; //Reference to main queue context

[moc performBlock:^{

[moc performBlock:^{

NSError *error = nil;

if (![moc save:&error]) {

NSLog(@"Failed to save: %@\n%@", [error localizedDescription],

[error userInfo]);

abort();

}

}];

NSFetchRequest *request = ...;

//... Define the request

NSError *error = nil;

NSArray *results = [moc executeRequest:request error:&error];

if (!results) {

NSLog(@"Failed to fetch: %@\n%@", [error localizedDescription],

[error userInfo]);

}

// Do something with the results

}];

And the -save: call would be executed after the data manipulation code. This effectively gives you a try/finally pattern.

Introducing -performBlockAndWait:

There are plenty of situations where you want to execute code on the main queue but you want your background queue (aka the non-main queue) to wait for that execution to be completed. That’s where the API -performBlockAndWait: is used. The parameters are exactly the same, but the behavior is a little bit different.

The main difference is that this API call will block the calling queue until the block is completed. This also means that -performBlockAndWait: is not re-entrant.

Working off the Main Queue

Now that threading with Core Data has been reduced to a binary question, the other type of NSManagedObjectContext we’ll look at is NSPrivateQueueConcurrencyType.

The primary difference between the two context types is what queue the context is associated with. When you’re working with an NSMainQueueConcurrencyType context, the context automatically associates itself with the main queue. When you initialize an NSPrivateQueueConcurrencyType context, the context will associate itself with a non-main queue that’s private to the context.

Private means that you can’t access that queue directly. Calling dispatch_sync or dispatch_async on that queue is against the API. The only way to interact with a private queue context is through -performBlock: and -performBlockAndWait:.

This difference also means that any interaction with the private queue context must be inside a -performBlock: or -performBlockAndWait: call. The three exceptions to that rule are -initWithConcurrencyType:, -setParentContext:, and -setPersistentStoreCoordinator:. Any other interaction with a private queue context must be wrapped in a block.

As with a main queue context, any objects created or retrieved from a private queue context can only be accessed on that private queue. If you attempt to access those objects outside the code block, then you’re violating the thread constraint rules of Core Data and will run into data integrity issues.

Interqueue Communication

Since objects created with or retrieved from a context can only be accessed on the queue associated with that context, the challenge becomes passing references to those objects between queues. This is arguably the biggest area where multiple threads with Core Data cause people the most issues.

If a reference to an object must be passed between queues, the best way to handle that hand-off is via the object’s -objectID property. This property is designed to be safe to access from multiple queues and is a unique identifier to the object.

A Note About the NSManagedObjectID

This unique identifier is generally only guaranteed to reference the object for the current life cycle of the application. While the -objectID can be persisted through various means, that approach isn’t recommended. There are several external actions that can void the reference and cause it to no longer function.

Once you have a reference to the objectID associated with an NSManagedObject, you can retrieve another reference to that NSManagedObject from another context through a few methods:

- -objectWithID: will return an object for any objectID passed to it. The danger with this method is that it’s guaranteed to return an object, even if it has to return an empty shell pointing to a nonexisting object. This can happen if an objectID is persisted and restored in a later application life cycle.

- -existingObjectWithID: error: is a preferred method to use because it will give back an object if it exists and will return nil if no object exists for the objectID. The slight negative with this method is that it can perform I/O if the object isn’t cached.

- -objectRegisteredForID: is the third option for object retrieval with objectID. This method will return the object if it’s already registered in the context that the method is being called against. Generally this method is only useful if you already know that the object has previously been fetched in the context.

In addition to object hand-off between queues (the passing of an object reference from one queue to another), there’s the handling of changes performed on a queue. By default, one context won’t notify another context if an object has been changed. It’s the responsibility of the developer to notify the other context of any changes. This is handled through the notification system.

Every time -save: is called against an NSManagedObjectContext, that context will broadcast a few notifications. The one that’s useful for cross-context notifications is NSManagedObjectContextDidSaveNotification. That notification is fired once the save has completed successfully, and the notification object that’s passed along includes all of the objects that were a part of the save.

If you have two contexts that you wish to keep in sync with each other, you can subscribe to this notification and then instruct the other context to consume the notification. For example, imagine that in your data controller you have two contexts—contextA and contextB—and you wish to keep them in sync. Once those contexts have been initialized, you can then subscribe to their notifications:

NSNotificationCenter *center = [NSNotificationCenter defaultCenter];

[center addObserver:self

selector:@selector(contextASave:)

forKey:NSManagedObjectContextDidSaveNotification

object:contextA];

[center addObserver:self

selector:@selector(contextBSave:)

forKey:NSManagedObjectContextDidSaveNotification

object:contextB];

In general you want notification observations to be as narrowly focused as possible. Although you could pass nil to the object: parameter, there would be no guarantee of who was broadcasting the notification, and you’d then need to filter inside the receiving method. By defining what objects we’re willing to accept notifications from, we don’t need to write defensive code in the receiving method and as a result we keep the receiving methods cleaner.

Once you see this notification, you can then consume it:

- (void)contextASave:(NSNotification*)notification

{

[self.contextB performBlock:^{

[self.contextB mergeChangesFromContextDidSaveNotification:notification];

}];

}

- (void)contextBSave:(NSNotification*)notification

{

[self.contextA performBlock:^{

[self.contextA mergeChangesFromContextDidSaveNotification:notification];

}];

}

With this implementation, every time contextA is saved, contextB will be notified and every time contextB is saved, contextA will be notified. Note that these -mergeChangesFromContextDidSaveNotification: calls should be wrapped in a -performBlock: to guarantee that they’re being processed on the proper queue.

It should be noted that while the -mergeChangesFromContextDidSaveNotification: is being consumed, the context is also notifying any of its observers that changes are taking place. This means that there can be side effects to this call.

For example, if contextA has an NSFetchedResultsController associated with it and that NSFetchedResultsController has some expensive cell drawing associated with it, we can expect to see a performance hotspot while consuming notifications. The reason for that is that the processing of these notifications isn’t threaded and the call to -mergeChangesFromContextDidSaveNotification: won’t return until all of the cells associated with that NSFetchedResultsController have completed their processing. Worse, since NSFetchedResultsController and the associated cells are on the main queue, the entire application’s user interface is effectively halted while these changes are being processed. This can result in some surprising user interface delays. The best way to avoid these types of performance issues is to keep the cells from taking too long to draw or to break the save notification up into smaller units that can be processed faster.

Parent/Child NSManagedObjectContext Design

In the previous section we saw how to consume changes from one context into another. With iOS 5.0, Core Data added another option to handle change notifications. Instead of having every context talk directly to NSPersistentStoreCoordinator, it’s possible to chain contexts together in a parent/child design.

Each child context will request objects from its parent context, and if that parent context doesn’t have the objects requested, it will pass that request up the chain until it hits NSPersistentStoreCoordinator and I/O is performed. However, if the parent context does have a reference to the desired objects, they’re returned to the child without I/O being performed.

Further, when a child makes a change to an object and then calls -save:, no I/O is performed as a result of that call. This means child contexts can make changes to objects and pass them to their parent without a performance penalty. In addition, the change notifications are handled automatically by the context so you don’t need to observe the notifications and then consume them.

To define a context as a child of another context, the setup is slightly different:

NSManagedObjectContext *moc = ...; //The existing context

NSManagedObjectContext *child = nil;

NSManagedObjectContextConcurrencyType type = NSPrivateQueueConcurrencyType;

child = [[NSManagedObjectContext alloc] initWithConcurrencyType:type];

[child setParentContext:moc];

Instead of the child being associated with NSPersistentStoreCoordinator via -setPersistentStoreCoordinator:, it’s associated with another context via a call to -setParentContext:. This tells NSManagedObjectContext that when it makes a request or saves an object, those requests and/or changes need to be pushed to the parent context.

The result is that it’s possible to have contexts that can make changes and push them up to another context without having to write out to disk via NSPersistentStoreCoordinator. If the child context is a child of the main queue context that’s associated with the user interface, the child context can process data on a background queue (by being created as an NSPrivateQueueConcurrencyType context), and when a save occurs, the user interface is notified immediately.

There are a few things to be aware of with this design. First, the changes aren’t actually persisted until the changes are passed to an NSPersistentStoreCoordinator. This means that if a change is made in a child context and passed to the main queue context but the main queue context isn’t saved before the application exits, those changes will be lost. If the application is set up to save the main queue context on exit, this isn’t normally an issue unless the application terminates abruptly.

The second issue is that this concept of automatic change notification goes in only one direction—from child to parent. If a change is made in the parent and even if the parent is saved, the child isn’t notified of that change. If the child already had a reference to the object that was changed, then the child wouldn’t receive those updates. A merge conflict could result if the child also attempted to alter that object.

There are many benefits to using this parent/child design. First, without having to listen for notifications, the amount of code that needs to be written is reduced. As part of that, the notification process is much faster. The contexts talking internally is going to be faster than code that you can write that talks to them externally.

Since child contexts request their data from their parent first, they can be much faster in retrieving objects if they are already in memory. For situations where you want to process data that already exists in memory, it makes sense to spawn a private child context and let that context process the data off the main queue. Ideally, no I/O will take place during that data processing.

Yet another benefit is in data consumption. When a request is made to the Internet and the resulting data needs to be consumed, it can be consumed with a child context with minimal impact to the main queue and user interface.

In the next few sections, we’ll demonstrate using the parent/child design.

Export Operation

In the first demonstration, we’ll add the ability to export recipes from Core Data so that they can be shared. We’ll create an NSOperation, which will create its own NSManagedObjectContext and use it to copy the selected recipes into a JSON structure, which can then be used by the application in several ways (uploaded to a server, emailed to a friend, and so on).

To implement this addition to our application, we need to make a few changes to the user interface. We want to add a button to the UINavigationBar that’s a generic action item. When the button is tapped, it will display an action sheet and give the user an option to mail the recipe, as shown here (we can add other options later).

To accomplish this, we first add the button in the storyboard and associate the button with a new method called -action:. Inside the -action: method, we construct a UIActionSheet and present it to the user.

Baseline/PPRecipes/PPRDetailViewController.m

- (IBAction)action:(id)sender;

{

UIActionSheet *sheet = [[UIActionSheet alloc] init];

[sheet addButtonWithTitle:@"Mail Recipe"];

[sheet addButtonWithTitle:@"Cancel"];

[sheet setCancelButtonIndex:([sheet numberOfButtons] - 1)];

[sheet setDelegate:self];

[sheet showInView:[self view]];

}

Once the user makes a choice with the UIActionSHeet, our PPRDetailViewController will get a callback as its delegate. If the user clicked Cancel, we simply return. Otherwise, we drop into a switch statement to handle the choice the user has made. In this example, we have only one choice available.

Baseline/PPRecipes/PPRDetailViewController.m

- (void)actionSheet:(UIActionSheet*)actionSheet

didDismissWithButtonIndex:(NSInteger)buttonIndex

{

if (buttonIndex == [actionSheet cancelButtonIndex]) return;

switch (buttonIndex) {

case 0: //Mail Recipe

[self mailRecipe];

break;

default:

NSLog(@"Unknown index: %li", (long)buttonIndex);

}

}

Building the Operation

When the user taps Mail Recipe, the switch statement calls our -mailRecipe method.

Baseline/PPRecipes/PPRDetailViewController.m

- (void)mailRecipe

{

PPRExportOperation *operation = nil;

operation = [[PPRExportOperation alloc] initWithRecipe:[self recipeMO]];

[operation setExportBlock:^(NSData *data, NSError *error) {

ZAssert(data || !error, @"Error: %@\n%@", [error localizedDescription],

[error userInfo]);

//Mail the data to a friend

}];

[[NSOperationQueue mainQueue] addOperation:operation];

}

In the -mailRecipe method, we construct a PPRExportOperation instance, passing it the recipe. We then give the PPRExportOperation a completion block. The completion block is issued to handle the results of the export operation. Note that the block receives back both an NSData and an NSError. If the operation was successful, the NSData is populated. If the operation failed for any reason, the NSData is nil, and the NSError is populated. In a production application, we’d want to respond to the error. For now, we have a developer-level logic check to capture the error.

The next step is to build the PPRExportOperation. The goal of the operation is to accept a single recipe and turn it into a JSON structure. When the export is complete, the operation will execute a completion block and pass the resulting data back to the caller. This gives us the following header:

Baseline/PPRecipes/PPRExportOperation.h

#import "PPRRecipeMO.h"

typedef void (^ExportCompletionBlock)(NSData *jsonData, NSError *error);

@interface PPRExportOperation : NSOperation

@property (nonatomic, copy) ExportCompletionBlock exportBlock;

- (id)initWithRecipe:(PPRRecipeMO*)recipe;

@end

The initializer for our export operation needs to retain only the NSManagedObjectID of the incoming NSManagedObject. Because the -initWithRecipe method is being called on the queue on which the the incoming NSManagedObject was created (the main queue), we can access its methods. Once we’re in our own -main method, we can no longer access it. However, the NSManagedObjectID is thread-safe and can cross the boundary. So, we grab it now and hold onto it in a property. We pass the entire NSManagedObject so that we can also grab a reference to the NSManagedObjectContext without having to explicitly request it.

Baseline/PPRecipes/PPRExportOperation.m

- (id)initWithRecipe:(PPRRecipeMO*)recipe;

{

if (!(self = [super init])) return nil;

[self setIncomingRecipeID:[recipe objectID]];

[self setParentContext:[recipe managedObjectContext]];

return self;

}

Once the operation is started, we must perform a couple of startup tasks. First, we need to create the child context.

Baseline/PPRecipes/PPRExportOperation.m

- (void)main

{

NSAssert([self exportBlock] != NULL, @"No completion block set");

NSManagedObjectContext *localMOC = nil;

NSUInteger type = NSPrivateQueueConcurrencyType;

localMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:type];

[localMOC setParentContext:[self parentContext]];

Now that we have a private NSManagedObjectContext, we need to kick off a -performBlockAndWait: and then retrieve a local copy of the recipe. In this situation, we want to have the operation block until the processing is completed so that the block isn’t destroyed by ending early.

A call to -objectWithID: returns a local copy of the NSManagedObject. Since we know the object already exists in the parent NSManagedObjectContext, it’s safe to assume that there will be no I/O in retrieving this object.

Conversion to JSON

Once we have a local copy of the recipe, we need to turn it into a JSON structure. To do that we must first convert the NSManagedObject into a dictionary. However, we don’t want to just grab the top-level object; we also want the author, the ingredients, and so on. To get all of those, we’ll have to do a bit of recursive work.

Baseline/PPRecipes/PPRExportOperation.m

- (NSDictionary*)moToDictionary:(NSManagedObject*)mo

{

NSMutableDictionary *dict = [NSMutableDictionary dictionary];

if (!mo) return dict;

NSEntityDescription *entity = [mo entity];

NSArray *attributeKeys = [[entity attributesByName] allKeys];

NSDictionary *values = [mo dictionaryWithValuesForKeys:attributeKeys];

[dict addEntriesFromDictionary:values];

The first part of the conversion involves grabbing all of the attributes (strings, numbers, dates) from the NSManagedObject and placing them into an NSMutableDictionary. By utilizing KVC, we can do this with a small amount of code. We first ask the NSEntityDescription for all of the attribute names, and then we call -dictionaryWithValuesForKeys: on the NSManagedObject. This returns a dictionary with all of the keys and values for the attributes. We then add the resulting dictionary into our NSMutableDictionary.

Baseline/PPRecipes/PPRExportOperation.m

NSDictionary *relationships = [entity relationshipsByName];

NSRelationshipDescription *relDesc = nil;

for (NSString *key in relationships) {

relDesc = [relationships objectForKey:key];

if (![[[relDesc userInfo] valueForKey:ppExportRelationship] boolValue]) {

DLog(@"Skipping %@", [relDesc name]);

continue;

}

Next, we must deal with the relationships. Just as with the attributes, we can find out the name of each relationship from the NSEntityDescription. By looping over the resulting NSDictionary, we can process each relationship. However, to make sure we aren’t in an infinite recursion, we check each relationship to see whether we should be following it. If we shouldn’t follow it, we simply skip to the next relationship.

Baseline/PPRecipes/PPRExportOperation.m

if ([relDesc isToMany]) {

NSMutableArray *array = [NSMutableArray array];

for (NSManagedObject *childMO in [mo valueForKey:key]) {

[array addObject:[self moToDictionary:childMO]];

}

[dict setValue:array forKey:key];

continue;

}

NSManagedObject *childMO = [mo valueForKey:key];

[dict addEntriesFromDictionary:[self moToDictionary:childMO]];

}

return dict;

}

When performing this copy, it’s easy to accidentally copy the entire Core Data repository. Because all our objects are linked via two-way relationships, if we built a recursive method to copy the objects and follow their relationships, we’d end up with a complete duplicate of all the recipes.

To prevent this, we added a check in each relationship copy. Whenever it follows a relationship, it first checks to make sure that the destination entity should be copied. We do this with a key-value pair in the data model. If there’s a key called ppExportRelationship and that key has a value of NO, we skip the relationship. By taking this step, we guarantee that the entity tree is copied in only one direction, as shown in the figure.

From the perspective of the current NSManagedObject, there are two types of relationships: to-one or to-many. If it’s a to-one relationship, we grab the object at the other end of the relationship and turn it into an NSDictionary for inclusion in our NSMutableDictionary.

If the relationship is a to-many, we need to iterate over the resulting set and turn each NSManagedObject into an NSDictionary and include it in a temporary array. Once we’ve iterated over all of the objects in the relationship, we add the entire array to our master NSMutableDictionary using the relationship name as the key.

Once all of the relationships have been processed, we return the NSMutableDictionary to the -main so that we can complete the final step.

Baseline/PPRecipes/PPRExportOperation.m

NSDictionary *objectStructure = [self moToDictionary:localRecipe];

data = [NSJSONSerialization dataWithJSONObject:objectStructure

options:0

error:&error];

}];

[self exportBlock](data, error);

}

At the end of the -main, we use the NSJSONSerialization class to turn the NSDictionary structure into a JSON structure. Note that the PPRExportOperation doesn’t care whether the serialization is successful. It just passes the result and the potential error off to the completion block. It’s up to the caller to handle any error in the processing.

Import Operation

The reverse of exporting recipes is to be able to import them. The experience we’re looking for is as follows:

1. The user receives a recipe in another application (for example, in Mail).

2. The user taps the recipe, and it opens in our application.

3. Our recipe application receives the data and consumes it.

To accomplish this workflow, we need to step into our UIApplicationDelegate and do a few updates.

Baseline/PPRecipes/PPRAppDelegate.m

- (BOOL)application:(UIApplication *)application

openURL:(NSURL *)url

sourceApplication:(NSString *)sourceApplication

annotation:(id)annotation

{

if ([[self dataController] isPersistenceInitialized]) {

[self consumeIncomingFileURL:url];

} else {

[self setFileToOpenURL:url];

}

return YES;

}

The first change adds the method -application: openURL: sourceApplication: annotation:. This method will be called whenever another application is requesting that we open a file. If our application has been running for a while and the Core Data stack is fully initialized, then we can process the file immediately. If the stack isn’t fully initialized (for instance, it freshly launched with the opening of the file), then we can store the NSURL and use it once the context has been initialized.

Baseline/PPRecipes/PPRAppDelegate.m

- (void)contextInitialized;

{

//Finish UI initialization

if (![self fileToOpenURL]) return;

[self consumeIncomingFileURL:[self fileToOpenURL]];

}

The next change takes place in -contextInitialized. Once the context is fully initialized, we can consume the NSURL that was passed in on the launch. Since we’re going to be consuming the NSURL from more than one potential entry point, we abstract away the handling into a -consumeIncomingFileURL: method. Thus, the -contextInitialized just needs to check whether there is an NSURL to consume and hand it off. Since -consumeIncomingFileURL: returns a pass or fail, we can add a logic check here to help capture failures during the development.

Building the Operation

The final change is to handle the consumption of the NSURL. We’ve already defined a method as -consumeIncomingFileURL:.

Baseline/PPRecipes/PPRAppDelegate.m

- (void)consumeIncomingFileURL:(NSURL*)url;

{

NSData *data = [NSData dataWithContentsOfURL:url];

PPRImportOperation *op = [[PPRImportOperation alloc] initWithData:data];

[op setMainContext:[[self dataController] managedObjectContext]];

[op setImportBlock:^(BOOL success, NSError *error) {

if (success) {

//Clear visual feedback

} else {

//Present an error to the user

}

}];

[[NSOperationQueue mainQueue] addOperation:op];

//Give visual feedback of the import

}

To consume the NSURL, we first load it into an NSData object. We can then pass the NSData instance off to a newly created PPRImportOperation. Once the operation is complete, we’ll display something graphically to the user and kick off the operation. The completion block for the operation checks to see whether there’s an error and then reports the error or dismisses the graphical status.

Our PPRImportOperation has a number of similarities to the PPRExportOperation. However, there’s also a bit more complexity.

Baseline/PPRecipes/PPRImportOperation.m

NSManagedObjectContext *localMOC = nil;

NSUInteger type = NSPrivateQueueConcurrencyType;

localMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:type];

[localMOC setParentContext:[self mainContext]];

As with our PPRExportOperation, we start off with the -main method. The first thing we want to do in the -main is construct the private NSManagedObjectContext and associate it as a child for the main context.

Baseline/PPRecipes/PPRImportOperation.m

[localMOC performBlockAndWait:^{

[self processRecipeIntoContext:localMOC];

}];

}

Consuming the JSON

Now that we have the private context constructed, we want to kick off the consumption of the JSON payload. We could just put the code directly in the -main method, but that would cause the method to get overly long, which can impact maintainability. Therefore, the -performBlockAndWait: method will call -processRecipeIntoContext: instead and pass in the context. Since the -processRecipeIntoContext: method is being called inside the block, it’s still on the proper queue and there’s no threading violation.

Baseline/PPRecipes/PPRImportOperation.m

- (void)processRecipeIntoContext:(NSManagedObjectContext*)moc

{

NSError *error = nil;

id recipesJSON = nil;

recipesJSON = [NSJSONSerialization JSONObjectWithData:[self incomingData]

options:0

error:&error];

if (!recipesJSON) {

[self importBlock](NO, error);

return;

}

NSManagedObject *recipeMO = nil;

NSEntityDescription *entity = [NSEntityDescription entityForName:@"Recipe"

inManagedObjectContext:moc];

if ([recipesJSON isKindOfClass:[NSDictionary class]]) {

recipeMO = [[NSManagedObject alloc] initWithEntity:entity

insertIntoManagedObjectContext:moc];

[self populateManagedObject:recipeMO fromDictionary:recipesJSON];

return;

} else {

NSAssert([recipesJSON isKindOfClass:[NSArray class]],

@"Unknown structure root: %@", [recipesJSON class]);

for (id recipeDict in recipesJSON) {

NSAssert([recipeDict isKindOfClass:[NSDictionary class]],

@"Unknown recipe structure: %@", [recipeDict class]);

recipeMO = [[NSManagedObject alloc] initWithEntity:entity

insertIntoManagedObjectContext:moc];

[self populateManagedObject:recipeMO fromDictionary:recipeDict];

}

if (![moc save:&error]) {

NSLog(@"Error saving context: %@\n%@", [error localizedDescription],

[error userInfo]);

abort();

}

}

}

We start the -processRecipeIntoContext: method by using the NSJSONSerializer to convert the NSData into a JSON structure. If that conversion fails, we call the completion block and let the operation finish.

Once we have the data in a JSON structure, we must check whether the top-level object is an NSArray or an NSDictionary. Adding this check makes the import operation more flexible and capable of handling the imported recipes. If the top-level object is an NSDictionary, we know there is a single recipe being imported and begin the import operation. If the top-level object is an NSArray, we iterate over the array and construct a recipe for each included dictionary.

For each dictionary (whether there are one or many), we construct an NSManagedObject and pass both the dictionary and the NSManagedObject into -populateManagedObject: fromDictionary:.

Baseline/PPRecipes/PPRImportOperation.m

- (void)populateManagedObject:(NSManagedObject*)mo

fromDictionary:(NSDictionary*)dict

{

NSManagedObjectContext *context = [mo managedObjectContext];

NSEntityDescription *entity = [mo entity];

NSArray *attKeys = [[entity attributesByName] allKeys];

NSDictionary *atttributesDict = [dict dictionaryWithValuesForKeys:attKeys];

[mo setValuesForKeysWithDictionary:atttributesDict];

In the first part of the -populateManagedObject: fromDictionary:, we want to populate all the attributes of the NSManagedObject. The process is the reverse of what we accomplished in the PPRExportOperation. Here are the steps:

1. We grab an NSArray of all the attribute names for the entity.

2. We then retrieve an NSDictionary from the passed-in NSDictionary of only those attributes and their associated values.

3. We take the resulting dictionary and hand it off to the NSManagedObject via the KVC method -setValuesForKeysFromDictionary:.

Baseline/PPRecipes/PPRImportOperation.m

NSManagedObject* (^createChild)(NSDictionary *childDict,

NSEntityDescription *destEntity,

NSManagedObjectContext *context);

createChild = ^(NSDictionary *childDict, NSEntityDescription *destEntity,

NSManagedObjectContext *context) {

NSManagedObject *destMO = nil;

destMO = [[NSManagedObject alloc] initWithEntity:destEntity

insertIntoManagedObjectContext:context];

[self populateManagedObject:destMO fromDictionary:childDict];

return destMO;

};

The next step is to create a block to avoid repeating ourselves later in this method. When we’re dealing with the creation of relationships, we need to create one or more child objects and associate them with the NSManagedObject we’re currently working on. Whether we’re creating one or many child objects, the steps are virtually identical. We’ll use a block to perform the identical portions of the process and avoid ending up with two copies of that code.

Therefore, the purpose of the block is to construct the new NSManagedObject and recursively call -populateManagedObject: fromDictionary: for the new NSManagedObject. The block makes the rest of this method easier to follow and maintain.

Baseline/PPRecipes/PPRImportOperation.m

NSDictionary *relationshipsByName = [entity relationshipsByName];

NSManagedObject *destMO = nil;

for (NSString *key in relationshipsByName) {

id childStructure = [dict valueForKey:key];

if (!childStructure) continue; //Relationship not populated

NSRelationshipDescription *relDesc nil;

relDesc = [relationshipsByName valueForKey:key];

NSEntityDescription *destEntity = [relDesc destinationEntity];

if (![relDesc isToMany]) { //ToOne

destMO = createChild(childStructure, destEntity, context);

[mo setValue:destMO forKey:key];

continue;

}

NSMutableSet *childSet = [NSMutableSet set];

for (NSDictionary *childDict in childStructure) {

destMO = createChild(childDict, destEntity, context);

[childSet addObject:destMO];

}

[self setValue:childSet forKey:key];

}

}

With the attributes populated, we now need to populate the relationships. We grab the dictionary from the NSEntityDescription that describes all of the relationships of our current NSManagedObject and then iterate over them. For each relationship, we check whether a value is set. If no value has been set, then there’s nothing to process. The iterator returns the key from the dictionary, which is the name of the relationship (and also the name of the accessor).

If there’s a value to process, we check whether the relationship is a to-one or a to-many. If it is a to-many, we can invoke our block and create the child NSManagedObject. We can then set the resulting NSManagedObject using KVC and the key from our relationship dictionary.

If the relationship is a to-many, we kick off an iterator to step over the collection and create one NSManagedObject for each NSDictionary inside the collection.

Once we’ve walked the entire JSON structure, the final step is to save the local NSManagedObjectContext. That save results in the changes being pushed directly to the main NSManagedObjectContext and the user interface is updated accordingly.

Asynchronous Saving

One of the biggest issues with threading and Core Data has to do with thread blocking. No matter how cleverly you write the import and export operations, sooner or later you need to block the main queue to let the main NSManagedObjectContext talk to the NSPersistentStoreCoordinator and save changes out to disk.

Fortunately, this is a solvable problem and requires a small change to the Core Data stack. If you start your Core Data stack with a private queue, NSManagedObjectContext, and associate it with NSPersistentStoreCoordinator, you can have the main NSManagedObjectContext as a child of the private NSManagedObjectContext. Also, when the main NSManagedObjectContext is saved, it won’t produce a disk hit and will instead be nearly instantaneous. From there, whenever you want to write to disk you can kick off a save on the private contact and get asynchronous saves.

Adding this ability to the application requires a relatively small change. First, you need to add a property to the PPRDataController to hold onto the new private NSManagedObjectContext. Next, you tweak the -initializeCoreDataStack a little bit.

Baseline/PPRecipes/PPRDataController.m

NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"DataModel"

withExtension:@"momd"];

NSAssert(modelURL != nil, @"Failed to find model URL");

NSManagedObjectModel *mom = nil;

mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

NSPersistentStoreCoordinator *psc = nil;

psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];

NSManagedObjectContext *moc = nil;

NSManagedObjectContext *writer = nil;

NSUInteger type = NSPrivateQueueConcurrencyType;

writer = [[NSManagedObjectContext alloc] initWithConcurrencyType:type];

[writer setPersistentStoreCoordinator:psc];

type = NSMainQueueConcurrencyType;

moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:type];

[moc setParentContext:writer];

[self setWriterContext:writer];

[self setManagedObjectContext:moc];

Before, we had one NSManagedObjectContext configured to be on the main queue and writing to the NSPersistentStoreCoordinator. Now we’ve added a new NSManagedObjectContext that’s of type NSPricateQueueConcurrencyType. We set the NSPersistentStoreCoordinator to that private queue. Finally, we construct our main queue, NSManagedObjectContxt. Instead of handing off the NSPersistentStoreCoordinator to the main context, we give it to the parent: the private queue context.

With that change, any saves on the main NSManagedObjectContext will push up the changes only to the private queue NSManagedObjectContext. No writing to the NSPersistentStoreCoordinator occurs. However, there are times when we really do want to write to disk and persist our data changes. In that case, a couple of other changes are in order.

Baseline/PPRecipes/PPRDataController.m

- (void)saveContext

{

NSManagedObjectContext *moc = [self managedObjectContext];

[moc performBlockAndWait:^{

if (![moc hasChanges]) return;

NSError *error = nil;

if (![moc save:&error]) {

NSLog(@"Failed to save: %@\n%@", [error localizedDescription],

[error userInfo]);

abort();

}

}];

moc = [self writerContext];

[moc performBlock:^{

if (![moc hasChanges]) return;

NSError *error = nil;

if (![moc save:&error]) {

NSLog(@"Failed to save: %@\n%@", [error localizedDescription],

[error userInfo]);

abort();

}

}];

}

Previously in our -saveContext method, we checked to make sure we had an NSManagedObjectContext and that it had changes. In this new implementation, we first call -performBlockAndWait: on the main queue context and, inside that block, check to see if there are any changes. If there are no changes, then the block terminates. If there are changes, we call -save: on the main queue context and check for errors.

Once the main queue context has been saved (remember that -performBlockAndWait: blocks), we can kick off a save on the private queue context. Since we specifically want this save to be asynchronous, we call -performBlock:. Inside the -performBlock: the actions are the same. We check to see if there are any changes and, if there are, we call -save: and check for errors.

Because we use -performBlock: against the private queue context, the main queue is not blocked while data is being written out to disk. The application continues to perform with no delays in the user interface.

Debug Concurrency Checking

One of the issues when dealing with any kind of threading is knowing whether or not you got it right. Threading issues are tricky. Things will work in development, they will work in testing, and then every once in a while they stop working in production. Core Data has suffered from this problem as much as any other framework.

Fortunately, as of iOS 8 and OS X10.10 Core Data now has a solution to the problem. You can now turn on a debug flag that will cause an exception if a Core Data object is accessed from the wrong queue.

This flag can be turned on as a runtime argument, and I strongly recommend that it be turned on for the debug configuration on every Core Data project.

To turn it on:

1. Edit the schema of your project and select the Run target.

2. Select the Arguments tab.

3. Add a new Arguments Passed On Launch and add -com.apple.CoreData.ConcurrencyDebug 1.

With that flag turned on during debug, the Core Data framework will throw an exception whenever the threading rules of Core Data are violated. If you combine that debug flag with the All Exceptions breakpoint, your application will halt on the exact line of code that’s violating the threading rules.

Wrapping Up

Threading is an extremely difficult subject. Very few developers get it right, and even those few still manage to make mistakes in this area. Threading is arguably one of the hardest concepts in computer science.

Threading with Core Data used to be extremely difficult to get right. Now with having only main queue and private queue contexts, it has gotten a lot simpler. It boils down to a simple Boolean decision. If you’re working with the UI, you should be using a main queue context. If you aren’t, then you should be using a private queue context.

However, even with this threading model you may encounter a situation when a lot of work needs to be done on the data in a short period of time. For those very rare instances, the Core Data framework has two APIs to handle bulk changes to the data structure. We discuss them in the next chapter.

Copyright © 2016, The Pragmatic Bookshelf.

Chapter 7

Bulk Changes

Over the years that Core Data has been in production, there have been a few complaints about the framework that struck home and were accurate. Easily the most well-known complaint was regarding the ability to change a value in a large number of objects without requiring those objects to all be loaded into memory and then persisted back out to disk. The second most well-known complaint was about deleting a large number of objects. Again, the desire is to delete a large number of objects without having to load them into memory and then write back out to the persistent store again.

Both of these complaints only apply to the NSSQLite store. Since atomic stores such as the binary store require all of the data to be in memory, there’s no issue with doing bulk changes or bulk deletes. But with the SQLite store, either of these changes can be incredibly CPU, disk, and memory intensive.

With the introduction of iOS 8.0 and OS X Yosemite, the first complaint was addressed. With the introduction of iOS 9.0 and OS X El Capitan, the second complaint was addressed.

Running with Scissors

Both of these APIs work by making changes directly on disk. When we use either of these APIs, Core Data will construct the appropriate SQL calls and then pass them to SQLite. Nothing gets loaded into memory and therefore the API is executed very quickly—just slightly slower than SQLite itself.

If we can just make changes and/or deletes on disk and avoid having to load them all into memory, why don’t we just do that all the time?

This API comes at a fairly significant cost. The changes that we make on disk aren’t passed to the NSManagedObjectContext instances in our application.

This means that we can very easily make a change to the data on disk and then our NSManagedObjectContext will try to make a different change and cause issues. When the first API was introduced, Apple likened these APIs to running with scissors. You can do it, but the risk is greater.

First, data validation is performed in memory. When we make a change directly on disk we’re bypassing the validation steps that Core Data normally performs. This means we can break a relationship and have dangling references, we can inject data that’s not valid, and so forth. Worse, our application won’t notice the issue until it attempts to load the data later and then the user is left in a bad state.

Second, when the changes are made on disk, the version number of the object is updated (as it should be). However, since nothing in memory knows of this change, the version number in memory won’t match. If Core Data were to attempt to do a save of an object in this state, a merge conflict would result with a potentially negative outcome.

And of course there’s the obvious issue: our user interface won’t know about the change and therefore the older data will still be displayed.

We can address these issues, but doing so requires more code on our part. Let’s start by looking at a bulk update.

Doing Bulk Updates

Doing a bulk update isn’t a common event in most application life cycles. Selecting a large number of emails, or a large number of news items, and marking them as read is a common example of doing a bulk update. Although these situations do occur, they are unusual and shouldn’t be considered a core function of the application. Bulk updates are generally used to get us out of a coding or design “corner.”

In our recipes application, we’re going to use the bulk update API to change the values of some of our recipes on the first launch after a migration. When we migrate the application to the fourth version, we’ll add a Boolean to indicate whether it’s a favorite recipe; the default for all recipes is NO. Once the migration is complete, we then want to go through all of the recipes and change that default to YES for some of them.

To start with, we want to detect if this change has already been made. There are several ways to accomplish this, and we’ve used other methods in the past. In this demonstration, we’re going to use the metadata that’s contained with the persistent store to determine whether the change has already been processed. This change to the initialization of our Core Data stack determines whether we need to do any post-migration processing.

Batch/PPRecipes/PPRDataController.m

dispatch_queue_t queue = NULL;

queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{

NSFileManager *fileManager = [NSFileManager defaultManager];

NSURL *storeURL = nil;

storeURL = [[fileManager URLsForDirectory:NSDocumentDirectory

inDomains:NSUserDomainMask] lastObject];

storeURL = [storeURL URLByAppendingPathComponent:@"PPRecipes.sqlite"];

NSMutableDictionary *options = [[NSMutableDictionary alloc] init];

[options setValue:[NSNumber numberWithBool:YES]

forKey:NSMigratePersistentStoresAutomaticallyOption];

[options setValue:[NSNumber numberWithBool:YES]

forKey:NSInferMappingModelAutomaticallyOption];

NSError *error = nil;

NSPersistentStore *store = nil;

store = [psc addPersistentStoreWithType:NSSQLiteStoreType

configuration:nil

URL:storeURL

options:options

error:&error];

if (!store) {

ALog(@"Error adding persistent store to coordinator %@\n%@",

[error localizedDescription], [error userInfo]);

}

NSDictionary *metadata = [store metadata];

if (!metadata[FAVORITE_METADATA_KEY]) {

[self bulkUpdateFavorites];

}

Every persistent store contains metadata. The metadata resolves to a NSDictionary that we can query. We can also update this metadata as needed.

In this part of the code, we’re looking for a key named FAVORITE_METADATA_KEY. If that key exists, then we know that this particular bit of post-processing has already been done. If the key is missing, we need to perform the task.

Batch/PPRecipes/PPRDataController.m

- (void)bulkUpdateFavorites

{

NSManagedObjectContext *moc = [self writerContext];

[moc performBlock:^{

NSBatchUpdateRequest *request = nil;

NSMutableDictionary *propertyChanges = nil;

NSPredicate *pred = nil;

NSBatchUpdateResult *result = nil;

NSError *error = nil;

request = [[NSBatchUpdateRequest alloc] initWithEntityName:@"Recipe"];

NSDate *aMonthAgo = [self dateFrom1MonthAgo];

pred = [NSPredicate predicateWithFormat:@"lastUsed >= %@", aMonthAgo];

[request setPredicate:pred];

propertyChanges = [NSMutableDictionary new];

propertyChanges[@"favorite"] = @(YES);

[request setPropertiesToUpdate:propertyChanges];

[request setResultType:NSUpdatedObjectIDsResultType];

result = [moc executeRequest:request error:&error];

if (!result) {

ALog(@"Failed to execute batch update: %@\n%@",

[error localizedDescription], [error userInfo]);

}

//Notify the contexts of the changes

[self mergeExternalChanges:[result result] ofType:NSUpdatedObjectsKey];

The -bulkUpdateFavorites method is where we’re using the bulk update API. Once we are positive that we are executing on the proper queue for our main NSManagedObjectContext, we start off by creating a new NSBatchUpdateRequest. The NSBatchUpdateRequest is a subclass of NSPersistentStoreRequest, which is a class that was introduced in OS X 10.7 and iOS 5. An NSBatchUpdateRequest contains all of the properties that Core Data needs to execute our update directly on disk. First, we initialize the request with the name of the entity we want to access. We then pass it the predicate to filter the entities that will be updated.

In this example, we’re going to find all the recipe entities that have been used in the last month and mark those as favorites. We construct a date object that represents one month ago and then pass that to the predicate and then pass the predicate into the NSBatchUpdateRequest.

In addition to the predicate, we need to tell Core Data what properties need to be changed. We do this with a NSDictionary, where the key is the property to change and the value is the new value to apply to the entity. As you can see, we don’t have a lot of control over the changes here. There’s no logic that we can apply. These are simple, brute-force data changes at the database/persistent store level.

Once we pass the dictionary to NSBatchUpdateRequest via -propertiesToUpdate, we can define what kind of result we want back. We have three options:

- NSStatusOnlyResultType, which won’t return anything. If we aren’t going to do anything with the response, there’s no reason to ask for one.

- NSUpdatedObjectIDsResultType, which will give us the NSManagedObjectIDs for each changed entity. If we’re going to notify the application of the changes, then we’ll want these to do the notification.

- NSUpdatedObjectsCountResultType, which will give us a simple count of the number of entities altered.

In this example, we’ll walk through updating the user interface of the changes, so we’ll ask for the NSManagedObjectID instances back.

Once we have the NSBatchUpdateRequest fully constructed, we can then hand it off to any NSManagedObjectContext we want for processing. Here I’m using the writer context because it’s closest to the NSPersistentStoreCoordinator. But since this API doesn’t notify the NSManagedObjectContext of the change, it really doesn’t matter which context we use.

The call to -executeRequest: error: returns a simple id, and it’s up to us to know what the call is giving back to us. Since we set the -resultType to be NSUpdatedObjectIDsResultType, we know that we’re going to be getting an NSArray back.

If we get back a nil from this API call, we know that there was an error and we can respond to that error. As always in the code in this book, we’re going to treat the error as a fatal condition and crash. How to respond to those errors is a business decision determined by your application’s design and requirements.

The call to -executeRequest: error: is a blocking call. This means that the call can take a significant amount of time—still far less than loading all of the objects into memory, performing the change, and saving them back out to disk, but it will take some time. This is another argument for using the API against the private writer context instead of the context that the user interface is associated with.

Notifying the Application of Changes

Once we’ve made changes on disk, we need to notify the contexts of these changes.

In this particular example, doing so isn’t strictly necessary. Most likely the user interface hasn’t touched any of these objects yet. If the objects haven’t been loaded into memory, there’s no risk of a conflict. However, it’s best that we don’t assume they haven’t been loaded yet. Users can be very clever.

There are two basic ways to notify our NSManagedObjectContext instances of the changes. We can reset each object individually in each NSManagedObjectContext that it might be associated with, or we can use the new API that was added in iOS 9.0. Let’s look at the harder way first.

Manual Object Refreshing

If the situation calls for it, we can instruct each instance of NSManagedObject to refresh individually. This might make sense if we have a view that’s observing one specific instance of an entity or we have a user interface that’s watching a small subset of objects.

Batch/PPRecipes/PPRDataController.m

- (void)manuallyRefreshObjects:(NSArray*)oIDArray;

{

NSManagedObjectContext *moc = [self managedObjectContext];

[moc performBlockAndWait:^{

[oIDArray enumerateObjectsUsingBlock:^(NSManagedObjectID *objectID,

NSUInteger index, BOOL *stop) {

NSManagedObject *object = [moc objectRegisteredForID:objectID];

if (!object || [object isFault]) return;

[moc refreshObject:object mergeChanges:YES];

}];

}];

}

The -manuallyRefreshObjects: method accepts an NSArray of NSManagedObjectID instances and walks through that array. Because this method is going to be working with the NSManagedObjectContext that’s on the main queue, we want a guarantee that our code will also be executed on the main queue. Therefore, we start by executing the code in a -performBlock: to ensure we’re on the correct queue.

Inside the block we iterate over the array of NSManagedObjectID instances and retrieve a reference to the associated NSManagedObject. Note that we’re using -objectRegisteredForID: in this method. -objectRegisteredForID: will only return a non-nil result if the object is registered with the referenced NSManagedObjectContext. If it isn’t referenced, we certainly don’t want to load it, so this method is a perfect fit. From that call we need to see if we got an object back and that it isn’t a fault. If it is a fault, we don’t need to refresh it because the values aren’t in memory.

Once we confirm the NSManagedObject is registered and isn’t a fault, we call -refreshObject: mergeChanges:, which will force the object to reload from the persistent store.

That’s a fair amount of work for each individual NSManagedObject in the array. Fortunately, there’s an easier way.

Remote Notifications

As part of the update for iOS 9.0 and OS X 10.11, the Core Data team gave us a new class method to handle this situation. We can now call +[NSManagedObjectContext mergeChangesFromRemoteContextSave: intoContexts:] to handle changes that occurred outside our Core Data stack.

The method accepts a dictionary of arrays of NSManagedObjectID instances (either true NSManagedObjectID objects or NSURL representations of them) and also accepts an array of NSManagedObjectContext instances.

Like traditional NSManagedObjectContextDidSaveNotification calls, we can pass in objects that are in three possible states: inserted, updated, and deleted.

Batch/PPRecipes/PPRDataController.m

- (void)mergeExternalChanges:(NSArray*)oIDArray ofType:(NSString*)type

{

NSDictionary *save = @{type : oIDArray};

NSArray *contexts = @[[self managedObjectContext], [self writerContext]];

[NSManagedObjectContext mergeChangesFromRemoteContextSave:save

intoContexts:contexts];

}

Note in this method that we aren’t concerned with what queue things are being executed on. The API call handles that for us. Also note that we’re able to update all of the contexts that exist in our application at once.

This method will basically do the same thing that we did in our manual method. But this method will handle it for every context we give it, and it’s faster than our manual method. This is the recommended way to consume remote notifications. With this method, our original bulk update call can easily notify the rest of the application of the changes.

Bulk Deletes

The API for handling bulk deletes was added to Core Data after bulk updates. Therefore, the API is very similar.

Batch/PPRecipes/PPRDataController.m

- (void)deleteOldRecipes

{

NSManagedObjectContext *moc = [self writerContext];

[moc performBlock:^{

NSDate *yearOld = [self dateFrom1YearAgo];

NSBatchDeleteRequest *request = nil;

NSFetchRequest *fetch = nil;

NSBatchDeleteResult *result = nil;

NSPredicate *predicate = nil;

NSError *error = nil;

fetch = [NSFetchRequest fetchRequestWithEntityName:@"Recipe"];

predicate = [NSPredicate predicateWithFormat:@"lastUsed <= %@", yearOld];

[fetch setPredicate:predicate];

request = [[NSBatchDeleteRequest alloc] initWithFetchRequest:fetch];

[request setResultType:NSBatchDeleteResultTypeObjectIDs];

result = [moc executeRequest:request error:&error];

if (!result) {

ALog(@"Failed on bulk delete: %@\n%@",

[error localizedDescription], [error userInfo]);

}

[self mergeExternalChanges:[result result] ofType:NSDeletedObjectsKey];

}];

}

The first difference, other than the fact that we’re using NSBatchDeleteRequest, is in the initialization of the request. NSBatchDeleteRequest is initialized with an NSFetchRequest rather than an entity name or description. This means we need to build an NSFetchRequest before we can initialize the NSBatchDeleteRequest. I prefer this approach because it matches what we are already accustomed to with NSFetchedResultsController.

Once the initialization of the NSBatchDeleteRequest is completed, the rest is the same. We call -executeRequest: error: against an instance of NSManagedObjectContext, which will be a blocking call, and then we can consume the results.

The NSBatchDeleteRequest has the same three types of results as the NSBatchUpdateRequest, so we can choose how to handle the results. Assuming we want to notify the rest of our application that some objects have been deleted, this implementation used the NSBatchDeleteResultTypeObjectIDs and handed off those NSManagedObjectID instances to the -mergeExternalChanges: ofType: method that we used previously.

Things to Consider When Using the Bulk Change APIs

Just because we have these APIs available to us does not mean that we should be using them without serious consideration. Several factors impact our decision to use these APIs.

How Fast Is “Faster”?

Doing massive changes to the underlying persistent store will never be instantaneous. No matter what type of database we’re working with, this is going to take time. However, compared to the way that we’d “normally” do this type of work with Core Data, these APIs are significantly faster.

Testing against massive persistent stores have shown at least an order of magnitude speed improvement over the traditional way of doing bulk changes. If an operation took 9 seconds previously via loading objects into memory and then writing them back out, it’d take less than a second using these bulk operations.

Although these APIs don’t fall into the realm of a “silver bullet” solution, they’re a massive improvement for those rare edge cases when they’re needed.

Bad Data

Data validation happens at the object level. While we still can’t shove binary data into a property designed for a number, we can put bad data into the database and it will work.

Worse, if we put bad data into the database, data that would normally fail validation, we won’t know that it’s bad until the application attempts to both load and then resave that data.

This would be a jarring experience for the user. Imagine loading a recipe, changing the name of it (or just flagging it as being used), and having the application crash or give the user an error saying it’s unable to save the recipe.

We need to take care to avoid this potentially very poor user experience. As always, testing rules supreme here.

Merge Conflicts

The one thing that these bulk updates do for us is update the version number of the entity in the persistent store. That version number (which is never exposed to us) is used to determine whether something other than the current NSPersistentStoreCoordinator was the last object to write to that entity. This is part of the merge conflict detection system that’s a component of Core Data.

If the application later attempts to save that same object and that version number doesn’t match, Core Data will attempt to do a merge. If our merge policy is still set to error, our application will present an error that needs to be handled. If the merge policy causes what’s in memory to trump what’s on disk, our changes are overwritten.

This can be mitigated fairly easily using the -mergeChangesFromRemoteContextSave: intoContexts: method that we used in our examples.

No Warnings of Data Changes

This bears repeating. If we don’t explicitly tell our application that we changed data on disk, our application won’t know about those changes.

If we make our changes before the user interface (or any other part of our application) touches the data, then we’re fine. But that’s a tightwire act that’s just waiting for a fall. Better to use the merge API we discussed and be certain.

Wrapping Up

Ideally we’ll never need these APIs. They were added to Core Data to handle those truly edge cases where a requirement came in that was unexpected and the data structure wasn’t implemented to handle it.

An API that exists and is rarely used is far better than an API that doesn’t exist and is sorely missed. Fortunately for us, these now exist and are not onerous to use.

Copyright © 2016, The Pragmatic Bookshelf.

Chapter 8

Using Core Data with iCloud

Starting with iOS 5.0 and Mac OS X 10.7 Lion, Apple has added the ability to sync a Core Data application to iCloud. For developers, that means we can easily add cloud sharing of our application’s data without the need to build our own servers. It also means our applications can share data between devices and computers.

iCloud integration allows developers to sync data between any number of computers and devices; we can sync between an iPhone application, an iPad application, a Mac application, or any combination thereof. In fact, we can add the ability to sync with our very first application, and new clients added later will automatically sync once we make them available to our users.

There are two approaches to adding iCloud integration to a Core Data application on iOS. We can just add a few options to our NSPersistentStore, or we can use the new API called UIManagedDocument. Currently, on OS X we don’t have a full counterpart to UIManagedDocument and therefore we have only one option for iCloud integration, which will be discussed later in this chapter.

If we have an existing application, the choice is simple: adding the options to the NSPersistentStore has the smallest impact on our code base. However, in order to properly move everything into iCloud, we have to do some additional work, as outlined in Migrating an Existing Application.

For a new application, it’s worth looking at UIManagedDocument and deciding whether it makes sense for the particular type of application you’re designing. Once the choice between using a UIManagedDocument or a standard Core Data stack has been made, the behavior of iCloud is the same. Therefore, we’ll start by examining UIManagedDocument and then look at wiring iCloud into a standard stack. Once we’re past those differences, we’ll take a deeper dive into the other details of using iCloud.

Introducing the UIManagedDocument

With the introduction of iCloud, Apple introduced a new API called UIDocument. UIDocument is designed to be an abstract parent class that makes it easy to integrate applications with iCloud. One of the Core Data API changes for iOS 6.0 is UIManagedDocument, a subclass of UIDocument.

Fundamentally, the biggest advantage of using UIManagedDocument is the ability to abstract away the saving and state of your Core Data stack. With UIManagedDocument, saving is handled automatically and generally occurs asynchronously. You can request saves to occur more frequently than the autosave handles, but in general you shouldn’t need to do that. In addition to managing the saving of the Core Data stack, UIManagedDocument has added features that allow you to store files outside of the persistent store that will also be pushed to iCloud.

UIManagedDocument is meant to be used in applications that have a document design. Pages, Numbers, Omnigraffle, and so on are great examples of iOS applications that manage a form of document. Having said that, however, there’s nothing stopping you from using a UIManagedDocument as your single Core Data stack enclosure. It’s not specifically designed for a single stack design, but it will work. It’s even appealing in some ways, since it abstracts out the creation of the stack.

iCloud/PPRecipes/PPRDataController.m

dispatch_queue_t queue;

queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{

NSFileManager *fileManager = [NSFileManager defaultManager];

NSURL *storeURL = nil;

storeURL = [[fileManager URLsForDirectory:NSDocumentDirectory

inDomains:NSUserDomainMask] lastObject];

storeURL = [storeURL URLByAppendingPathComponent:@"PPRecipes"];

NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];

The first step in constructing a UIManagedDocument is to resolve the file URL where we’ll store the document and determine what the file URL is going to be for iCloud. This first step, configuring where we are saving the data, is virtually the same as when we construct a standard Core Data stack. We determine where the documents directory is located via a call to the NSFileManager, then we append a path component to the end to identify our document. In this case, the document is called PPRecipes.

The second URL—the iCloud URL—is a bit different. ‑URLForUbiquityContainerIdentifier: is a new addition to the NSFileManager that came with iCloud. This call requests a URL in which to store information that iCloud is going to use to sync the NSPersistentStore. If iCloud is disabled on the device (iOS or OS X), this call returns nil. Once we have a URL and it’s not nil, we know iCloud is available, and we need to configure it.

It’s the second call that has an interesting issue—specifically, this call can take an indeterminate amount of time. If iCloud isn’t available or if the directory structure had previously been constructed, this call could return nearly instantaneously. However, if iCloud is enabled and the directory structure needs to be constructed, the call might take a significant amount of time—long enough that we need to worry about the watchdog killing it for taking too long to launch. Therefore, because of this method call, we need to wrap all of this construction in a dispatch queue and let it run asynchronously with the main thread.

Once we know whether iCloud is available, we can continue with our configuration.

Configuring iCloud

To add iCloud to a Core Data stack, we need to add some additional options to the NSPersistentStore when we add the NSPersistentStore to the NSPersistentStoreCoordinator.

iCloud/PPRecipes/PPRDataController.m

NSMutableDictionary *options = [[NSMutableDictionary alloc] init];

[options setValue:[NSNumber numberWithBool:YES]

forKey:NSMigratePersistentStoresAutomaticallyOption];

[options setValue:[NSNumber numberWithBool:YES]

forKey:NSInferMappingModelAutomaticallyOption];

if (cloudURL) {

cloudURL = [cloudURL URLByAppendingPathComponent:@"PPRecipes"];

[options setValue:[[NSBundle mainBundle] bundleIdentifier]

forKey:NSPersistentStoreUbiquitousContentNameKey];

[options setValue:cloudURL

forKey:NSPersistentStoreUbiquitousContentURLKey];

}

The first part of this code should be familiar. We create an NSMutableDictionary and add the options both to infer a mapping model and to attempt a migration automatically. From there, if iCloud is enabled, we need to add the iCloud URL to our options dictionary. However, we don’t want our document stored at the root of our iCloud sandbox. Rather, we want to create a directory under the root with the same name as the document we’re creating locally. Therefore, we’ll append “PPRecipes” to the end of the URL. Once the URL is defined, we need to add it to our options dictionary with the key NSPersistentStoreUbiquitousContentURLKey.

In addition to the URL for the storage location, we need to tell iCloud what data it’s going to sync. If we have a single application shared between iPhone and iPad, as in our current example, we can use the bundle identifier as a unique key to define what data is to be shared across the devices. However, if we’re also sharing data with a desktop application, the bundle identifier may not be appropriate. The data identifier is stored in the options dictionary with the key NSPersistentStoreUbiquitousContentNameKey.

The addition of these two keys is the bare minimum required to enable iCloud for an iOS application. With that information, the operating system creates a directory for the content, downloads any content that exists in the cloud, and begins syncing the data. However, as with the URL call, the initial download (or for that matter subsequent syncing) can take an indeterminate amount of time. If there’s nothing currently in the store, the creation of the directory structure will be virtually instantaneous. But if there’s data to download, it could take some time, depending on the speed of the network connection and the amount of data. Therefore, the application needs to be able to handle a delay in the creation of the persistent store. There are many ways to deal with this delay, and that’s an exercise left to the user experience experts; handling the blending of synchronous and asynchronous actions is always a problem to be dealt with as part of the user experience.

Building the UIManagedDocument

Once the options dictionary has been constructed, it’s time to build the UIManagedDocument. The construction of the document itself is short.

iCloud/PPRecipes/PPRDataController.m

UIManagedDocument *document = nil;

document = [[UIManagedDocument alloc] initWithFileURL:storeURL];

[document setPersistentStoreOptions:options];

NSMergePolicy *policy = [[NSMergePolicy alloc] initWithMergeType:

NSMergeByPropertyObjectTrumpMergePolicyType];

[[document managedObjectContext] setMergePolicy:policy];

void (^completion)(BOOL) = ^(BOOL success) {

if (!success) {

ALog(@"Error saving %@\n%@", storeURL, [document debugDescription]);

return;

}

if ([self initBlock]) {

dispatch_sync(dispatch_get_main_queue(), ^{

[self initBlock]();

});

}

};

if ([[NSFileManager defaultManager] fileExistsAtPath:[storeURL path]]) {

[document openWithCompletionHandler:completion];

return;

}

[document saveToURL:storeURL

forSaveOperation:UIDocumentSaveForCreating

completionHandler:completion];

[self setManagedDocument:document];

});

Constructing the UIManagedDocument is a case of calling +alloc and then ‑initWithFileURL: and passing in the storeURL that we previously constructed. Once the UIManagedDocument is initialized, we can set the options for the NSPersistentStore via a call to ‑setPersistentStoreOptions:. Note that we don’t have the ability to add more than one NSPersistentStore to a UIManagedDocument.

We also want to take this opportunity to set the merge policy for the UIManagedDocument. This setting is actually performed on the NSManagedObjectContext directly.

Unlike when we construct a straight Core Data stack, though, the initialization of the UIManagedDocument is not the end for us. We must now save it. We could save it later, but it’s best to put all of this initialization code in the same place rather than have it spread out across our UIApplicationDelegate. To save the UIManagedDocument, we must first discover if it already exists; based on that information, we can call the appropriate method.

Whether the UIManagedDocument existed before, the process is the same: we call a method on the UIManagedDocument and pass in a completion handler. Since that completion handler is the same no matter which method we call, we construct the completion handler and then determine which method to call.

In the completion handler, we check to see whether it completed successfully. If it wasn’t successful, we present an error to the user and perhaps try to recover from the error. If the completion was successful, we want to notify the AppDelegate that the UIManagedDocument has been initialized and that normal program flow can resume.

With the completion block constructed, we can now ask the NSFileManager if the file already exists; if it does, we call ‑openWithCompletionHandler: on the UIManagedDocument. If it doesn’t exist, we need to create it with a call to ‑saveToURL: forSaveOperation:UIDocumentSaveForCreating: completionHandler:. If this seems overly complicated, that’s because it is. This really should be abstracted away into the framework.

Observing Changes to the UIManagedDocument

Once our UIManagedDocument has been constructed, it can be quite useful to know its current state. Since the UIManagedDocument saves on its own accord, we won’t automatically know whether it’s clean or dirty. We need some kind of callback system in place to notify us. Fortunately, the UIManagedDocument does broadcast notifications when the state changes. By adding our UIApplicationDelegate as an observer to the notification UIDocumentStateChangedNotification, we’re notified of those changes and can act accordingly.

iCloud/PPRecipes/PPRAppDelegate.m

- (void)contextInitialized;

{

DLog(@"fired");

NSNotificationCenter *center = [NSNotificationCenter defaultCenter];

[center addObserver:self

selector:@selector(documentStateChanged:)

name:UIDocumentStateChangedNotification

object:[[self dataController] managedDocument]];

There are several places that we could start observing this notification; placing it in the ‑contextInitialized is a personal preference. It’s possible to start listening to it as part of the initialization of the UIManagedDocument, for example. When this notification fires, we receive the UIManagedDocument as the object of the notification. From the UIManagedDocument, we then respond accordingly.

iCloud/PPRecipes/PPRDataController.m

- (void)documentStateChanged:(NSNotification*)notification

{

switch ([[notification object] documentState]) {

case UIDocumentStateNormal:

DLog(@"UIDocumentStateNormal");

break;

case UIDocumentStateClosed:

DLog(@"UIDocumentStateClosed %@", notification);

break;

case UIDocumentStateInConflict:

DLog(@"UIDocumentStateInConflict %@", notification);

break;

case UIDocumentStateSavingError:

DLog(@"UIDocumentStateSavingError %@", notification);

break;

case UIDocumentStateEditingDisabled:

DLog(@"UIDocumentStateEditingDisabled %@", notification);

break;

case UIDocumentStateProgressAvailable:

DLog(@"UIDocumentStateProgressAvailable %@", notification);

break;

}

}

From the state of the UIManagedDocument, we can update our user interface to reflect that state and then let the user know what is going on with the underlying data.

Manually Saving a UIManagedDocument

By default, the UIManagedDocument works to ensure that our data is saved as frequently as makes sense.

The UIDocument design knows to listen for UIApplicationWillResignActiveNotification, UIApplicationDidEnterBackgroundNotification, and UIApplicationWillTerminateNotification notifications. When it receives one of these notifications, it saves. It also saves periodically during the life of the application. On average, these periodical saves take place every five minutes.

However, we know our application better than the frameworks do. We know when something nonrecoverable or vital has just occurred, and we can decide that a save is mandatory at a specific point. Fortunately, it’s possible to convey that need to UIManagedDocument.

iCloud/PPRecipes/PPRDataController.m

NSURL *fileURL = [[self managedDocument] fileURL];

[[self managedDocument] saveToURL:fileURL

forSaveOperation:UIDocumentSaveForCreating

completionHandler:^(BOOL success) {

//Handle failure

}];

The call to request a save is the same we used when we were initially creating the UIManagedDocument. Further, we can request the URL for the save directly from the UIManagedDocument. The only detail left is planning how to properly respond to a failed save.

Direct NSManagedObjectContext to iCloud

If you’ve been using Core Data for a while, you’ll feel right at home creating a Core Data stack; otherwise, this code will look similar to the stack we discussed in Chapter 2, Under the Hood of Core Data. The code to add iCloud to the Core Data stack is short, straightforward, and easy to add. This is good and bad. It’s good, in that it takes a small amount of effort to add iCloud to your Core Data--based application, but it’s bad because there aren’t very many options to configure, and it’s a one-size-fits-all design. If your data model is complex or if you have elements that you don’t want to sync, then lack of configurability will cause some interesting solutions. For example, if you have nonsyncable data, then you may need to split your data into more than one persistent store. Another situation that can feel limiting is if you have a very high churn rate in your data structures. iCloud prefers to have an opportunity to process the data, and a high rate of content creation or change can cause it to get backed up. In that situation, it may be necessary to “roll up” your data changes to decrease the number of entities being created or the frequency of saves. Reviewing your application’s activities in Instruments (as discussed in Chapter 5, Performance Tuning) will help you to determine whether you have strayed off the golden path.

Configuring iCloud

To integrate iCloud with our Core Data stack, we insert some additional options to the NSPersistentStore when we add the NSPersistentStore to the NSPersistentStoreCoordinator.

iCloud/PPRecipes/PPRDataController.m

NSMutableDictionary *options = [[NSMutableDictionary alloc] init];

[options setValue:[NSNumber numberWithBool:YES]

forKey:NSMigratePersistentStoresAutomaticallyOption];

[options setValue:[NSNumber numberWithBool:YES]

forKey:NSInferMappingModelAutomaticallyOption];

NSFileManager *fileManager = [NSFileManager defaultManager];

NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];

if (cloudURL) {

DLog(@"iCloud enabled: %@", cloudURL);

cloudURL = [cloudURL URLByAppendingPathComponent:@"PPRecipes"];

[options setValue:[[NSBundle mainBundle] bundleIdentifier]

forKey:NSPersistentStoreUbiquitousContentNameKey];

[options setValue:cloudURL

forKey:NSPersistentStoreUbiquitousContentURLKey];

} else {

DLog(@"iCloud is not enabled");

}

The first part of this code should be familiar. We create an NSMutableDictionary and add the options both to infer a mapping model and to attempt a migration automatically. From here, though, we’re in new territory. ‑URLForUbiquityContainerIdentifier: is a new addition to the NSFileManager that came with iCloud. This call requests a URL used to store information that iCloud is going to use to sync the NSPersistentStore. If iCloud is disabled on this device (or Mac OS X computer), this call will return nil. Once we have the URL and have established that it’s not nil, we know iCloud is available, and we can begin to configure it.

The URL we receive points to a file path; it looks something like this:

file://localhost/private/var/mobile/Library/

Mobile%20Documents/K7T84T27W4~com~pragprog~PPRecipes/

Notice this file path is outside our application sandbox. Even so, we have some control over what goes into this directory. For example, if we use a document-based application, we could append the name of the document onto this path so that each document is kept separate. In our current example, however, we’re going to create a single subdirectory for our Core Data stack. This will help us in the future if we decide to make changes or sync additional items. Once the URL is defined, we need to add it to our options dictionary with the key NSPersistentStoreUbiquitousContentURLKey.

In addition to the URL for the storage location, we must tell iCloud what data it should be syncing with. If we have a single application shared between iPhone and iPad, as in our current example, we can use the bundle identifier as a unique key to define what data is to be shared across the devices. However, if we’re also sharing data with a desktop application, the bundle identifier may not be appropriate. The data identifier is stored in the options dictionary with the key NSPersistentStoreUbiquitousContentNameKey.

Once again, the addition of those two keys is the bare minimum required to enable iCloud for an iOS application. With that information, the operating system creates a directory for the content, downloads any content that exists in the cloud, and starts syncing that data for us. However, that initial download (or for that matter subsequent syncing) can take an indeterminate amount of time. If there’s nothing currently in the store, the creation of the directory structure will be virtually instantaneous. However, if there’s data to download, it could take some time, depending on the speed of the network connection and the amount of data. As before, we must change how we add the NSPersistentStore to the NSPersistentStoreCoordinator.

Asynchronously Adding the NSPersistentStore

Prior to iOS 6.0 and Mac OS X Lion, we could add the NSPersistentStore to the NSPersistentStoreCoordinator directly on the main thread. While this was rarely recommended, it was extremely common. With the addition of iCloud, it’s really no longer an option. The process of configuring iCloud happens when we add the NSPersistentStore to the NSPersistentStoreCoordinator, and it happens before the call returns. If iCloud needs to download data and that download takes several seconds, our application will be unresponsive while the download occurs, and our application could be potentially killed from the operating system watchdog for taking too long to start up.

Currently, the best solution to this problem is to add the NSPersistentStore to the NSPersistentStoreCoordinator on a background thread. We can use dispatch queues and blocks to make this relatively painless.

iCloud/PPRecipes/PPRDataController.m

dispatch_queue_t queue;

queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{

NSMutableDictionary *options = [[NSMutableDictionary alloc] init];

[options setValue:[NSNumber numberWithBool:YES]

forKey:NSMigratePersistentStoresAutomaticallyOption];

[options setValue:[NSNumber numberWithBool:YES]

forKey:NSInferMappingModelAutomaticallyOption];

NSFileManager *fileManager = [NSFileManager defaultManager];

NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];

if (cloudURL) {

DLog(@"iCloud enabled: %@", cloudURL);

cloudURL = [cloudURL URLByAppendingPathComponent:@"PPRecipes"];

[options setValue:[[NSBundle mainBundle] bundleIdentifier]

forKey:NSPersistentStoreUbiquitousContentNameKey];

[options setValue:cloudURL

forKey:NSPersistentStoreUbiquitousContentURLKey];

} else {

DLog(@"iCloud is not enabled");

}

NSURL *sURL = nil;

sURL = [[fileManager URLsForDirectory:NSDocumentDirectory

inDomains:NSUserDomainMask] lastObject];

sURL = [sURL URLByAppendingPathComponent:@"PPRecipes-iCloud.sqlite"];

NSError *error = nil;

NSPersistentStoreCoordinator *coordinator = nil;

coordinator = [[self managedObjectContext] persistentStoreCoordinator];

NSPersistentStore *store = nil;

store = [coordinator addPersistentStoreWithType:NSSQLiteStoreType

configuration:nil

URL:sURL

options:options

error:&error];

if (!store) {

ALog(@"Error adding persistent store to coordinator %@\n%@",

[error localizedDescription], [error userInfo]);

//Present a user facing error

}

if ([self initBlock]) {

dispatch_sync(dispatch_get_main_queue(), ^{

[self initBlock]();

});

}

});

In this code block, we define the path for our SQLite file and gain a reference to the NSPersistentStoreCoordinator. From there, we add the NSPersistentStore to the NSPersistentStoreCoordinator. Assuming that is successful, we push another block back to the main queue and inform the application that the Core Data stack has been completed and is ready for use.

The reason we grab a fresh reference to the NSPersistentStoreCoordinator is one of safety. If we were to use the reference from the outer method, we’d be incrementing the retain count of the NSPersistentStoreCoordinator and potentially causing an unnecessary reference count issue. Since we can easily obtain a fresh reference to it, there’s no reason to use the reference from the outer method.

Likewise, once we’ve completed the construction of the NSPersistentStoreCoordinator, we want to be on the main thread (aka the UI Thread) when we call ‑contextInitialized so that the rest of the AppDelegate doesn’t need to dance with threads. Keeping all of the thread jumping in one place makes it easier to maintain.

Consuming Changes from iCloud

Whether we’re using a standard Core Data stack or a UIManagedDocument, we need to know when changes come in from iCloud. Changes will always come in asynchronously, and our NSManagedObjectContext won’t know about them. It is our responsibility to notify our NSManagedObjectContext of any incoming changes. To do that, we first need to listen for the change notification via the NSNotificationCenter.

iCloud/PPRecipes/PPRAppDelegate.m

NSString *name = nil;

name = NSPersistentStoreDidImportUbiquitousContentChangesNotification;

NSManagedObjectContext *moc = [[self dataController] managedObjectContext];

[center addObserver:self

selector:@selector(mergePSCChanges:)

name:name

object:[moc persistentStoreCoordinator]];

}

Just like with the UIDocumentStateChangedNotification for the UIManagedDocument, it’s a good idea to start listening for the NSPersistentStoreDidImportUbiquitousContentChangesNotification notifications after the Core Data stack (or UIManagedDocument) has been constructed. Therefore, we put the ‑addObserver: selector: name: object: call in the ‑contextInitialized method.

When the notification fires, it can be treated exactly as if a notification from an NSManagedObjectContext is coming in from another thread, as discussed in Chapter 6, Threading. Although the notification doesn’t contain actual NSManagedObject instances, it does contain NSManagedObjectID instances, and the NSManagedObjectContext knows how to consume them as well.

iCloud/PPRecipes/PPRDataController.m

- (void)mergePSCChanges:(NSNotification*)notification

{

NSManagedObjectContext *moc = [self managedObjectContext];

[moc performBlock:^{

[moc mergeChangesFromContextDidSaveNotification:notification];

}];

}

Under the Hood

Now that we’ve looked at how to integrate iCloud into our Core Data--based application, it’s helpful to understand exactly how it works under the hood.

Debug Output

Core Data has several logging levels that we can turn on to watch all of the SQL calls that are generated during an application’s life cycle. There are currently three levels to this debugging log, with level 1 being the least chatty and level 3 being the most chatty. We can set this debug output by adding the runtime parameter com.apple.CoreData.SQLDebug to our application and pass along with it the level we want to be set.

In addition to the Core Data debug setting, we can turn on an additional setting to watch all of the chatter between Core Data and iCloud. That additional setting is com.apple.coredata.ubiquity.logLevel. For an unknown reason, this logging code responds only to level 3, as shown here.

Looking at these logs, we can see there’s a tremendous amount of activity going on behind the scenes. We can also examine the frequency with which Core Data starts a sync with iCloud and use that information when we’re determining how often to generate saves from our application.

Transaction Logs

iCloud functions through transaction logs. Similar to how modern version control systems work, every time a save is performed by an “iCloud-backed” persistent store, a transaction log is created that details what happened in that save. These transaction logs are kept in order and pushed up to the cloud. On other devices, the transaction logs are received and played back onto the receiving persistent store. Through this process, each persistent store is kept in sync with the other. In addition, new persistent stores can be added to the mix by simply starting with an empty store and playing back the transactions one at a time until the new store is brought up-to-date.

If the application has seen a lot of activity, there can be a large number of transaction logs. The large number of transaction logs means any new device must take a significant amount of time to “catch up” with the current state of the persistent store. For this reason, Core Data cuts a new “baseline” on a regular interval. A baseline is effectively a “roll-up” of the transaction logs to a certain point. Any logs after the baseline point will pile up as before. A new store coming into the sync starts with a baseline instead of an empty database and consumes any transaction logs generated after the baseline. This strategy keeps the amount of time it takes to bring a new persistent store up to speed to a minimum.

Turning Off iCloud

Currently, there’s an outstanding issue when the user turns off iCloud. When this event occurs, our application receives no notification. The data simply goes away. Some developers have been successful in adding a “sentinel” file to the ubiquitous container (via the URL that gets resolved when we configure iCloud) and monitoring that file. If the file goes away, then we can decide how to handle the state change. Creating a sentinel file hasn’t been approved (or disallowed) by Apple, and it may not work in future versions of Core Data.

Ordered Relationships Are Not Allowed

If we integrate iCloud into our application, we can’t use the new ordered relationships that were added as of iOS 6.0 and OS X 10.8. iCloud integration does not work with ordered relationships. Ideally, this deficiency will be corrected at some point.

Handling Data Migration

A potential issue and surprise with iCloud integration is that only lightweight migration can be used in an iCloud integrated Core Data application. Heavy-weight migration (as in a migration that requires a mapping model) can’t be used if iCloud is enabled. With an iCloud-enabled NSPersistentStore, the mapping model will simply be ignored, and the migration fails. Therefore, particular care must be taken when you’re doing a migration to avoid crossing the limit of lightweight/inferred migration.

If a migration requires more complexity than a lightweight migration can handle, it’s recommended that a new ubiquitous container be set up, the migration be done with iCloud turned off, and then the data be loaded into the new container. To accomplish this, you would need to follow a procedure similar to the one discussed next.

Migrating an Existing Application

Because Core Data keeps track of changes via transaction logs, it’s impossible to just “turn on” iCloud in an existing application and expect all the data to get pushed into the cloud. A few other steps are necessary.

The first question when adding iCloud to an existing iOS application is whether the migration is necessary. There are two key criteria for answering this question.

- Is there any existing data to migrate?

- Has the migration already been performed?

Both of these questions can be answered easily if we do a simple filename change. For example, if our application has always used a SQLite file named PPRecipes.sqlite, then when we want to add iCloud integration to our application, we should start using a filename of PPRecipes-iCloud.sqlite. A simple “does this file exist?” check tells us whether we need to migrate our existing data.

If it’s not possible or reasonable to rename the file, the fallback option is to store a flag in the NSUserDefaults to let us know whether the migration has occurred. This option is second best for a couple of reasons.

- As we’ll demonstrate in a moment, the file needs to be moved anyway.

- NSUserDefaults classes tend to be a bit unreliable, especially during testing.

Assuming we’re going to use a file rename strategy to determine whether a migration is required, the first step is to look for the “old” filename to determine whether a migration is required. As part of this change to handle the migration, we’re going to refactor the NSPersistentStoreCoordinator initialization code somewhat to make it more maintainable with these additions.

iCloud/PPRecipes/PPRDataController.m

dispatch_queue_t queue;

queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{

NSMutableDictionary *options = [[NSMutableDictionary alloc] init];

[options setValue:[NSNumber numberWithBool:YES]

forKey:NSMigratePersistentStoresAutomaticallyOption];

[options setValue:[NSNumber numberWithBool:YES]

forKey:NSInferMappingModelAutomaticallyOption];

NSFileManager *fileManager = [NSFileManager defaultManager];

NSURL *docURL = nil;

docURL = [[fileManager URLsForDirectory:NSDocumentDirectory

inDomains:NSUserDomainMask] lastObject];

NSURL *storeURL = nil;

NSError *error = nil;

NSPersistentStoreCoordinator *coordinator = nil;

coordinator = [[self managedObjectContext] persistentStoreCoordinator];

NSPersistentStore *store = nil;

NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];

We start the changes at the top of the asynchronous dispatch queue. Notice that we’re setting the “universal” options only for the NSPersistentStore at this point. This step allows us to reuse the dictionary no matter what path we end up taking. We’re also obtaining our reference to the NSPersistentStoreCoordinator here, because that will be used through the rest of the block. Finally, we request the cloudURL from the NSFileManager so that we can start to determine how to add the NSPersistentStore to the NSPersistentStoreCoordinator.

Now we’re ready to make our first decision: is iCloud available or not?

iCloud/PPRecipes/PPRDataController.m

if (!cloudURL) {

storeURL = [docURL URLByAppendingPathComponent:@"PPRecipes.sqlite"];

store = [coordinator addPersistentStoreWithType:NSSQLiteStoreType

configuration:nil

URL:storeURL

options:options

error:&error];

if (!store) {

ALog(@"Error adding persistent store to coordinator %@\n%@",

[error localizedDescription], [error userInfo]);

//Present a user facing error

return;

}

if ([self initBlock]) {

dispatch_sync(dispatch_get_main_queue(), ^{

[self initBlock]();

});

}

return;

}

Now that we’ve added the migration code for iCloud, it’s actually the shorter path when iCloud isn’t enabled. Therefore, we’re going to respond to that decision first. If iCloud isn’t available, we look for the file named PPRecipes.sqlite and add it to the persistent store. If the file doesn’t exist, Core Data will create it. This is the traditional logic path.

Once the NSPersistentStore is added to the NSPersistentStoreCoordinator, we check to make sure it was successful and then notify our UIApplicationDelegate that the stack initialization is complete and return. It should be noted that it’s possible for the user to turn iCloud back off and be fully robust. We should check to see whether that situation occurred. If it did, we must migrate back off of iCloud. That decision branch is left as an exercise for the reader.

iCloud/PPRecipes/PPRDataController.m

storeURL = [docURL URLByAppendingPathComponent:@"PPRecipes-iCloud.sqlite"];

NSURL *oldURL = nil;

oldURL = [docURL URLByAppendingPathComponent:@"PPRecipes.sqlite"];

if ([fileManager fileExistsAtPath:[oldURL path]]) {

store = [coordinator addPersistentStoreWithType:NSSQLiteStoreType

configuration:nil

URL:oldURL

options:options

error:&error];

if (!store) {

ALog(@"Error adding OLD persistent store to coordinator %@\n%@",

[error localizedDescription], [error userInfo]);

//Present a user facing error

return;

}

}

cloudURL = [cloudURL URLByAppendingPathComponent:@"PPRecipes"];

[options setValue:[[NSBundle mainBundle] bundleIdentifier]

forKey:NSPersistentStoreUbiquitousContentNameKey];

[options setValue:cloudURL

forKey:NSPersistentStoreUbiquitousContentURLKey];

store = [coordinator migratePersistentStore:store

toURL:storeURL

options:options

withType:NSSQLiteStoreType

error:&error];

if (!store) {

ALog(@"Error adding OLD persistent store to coordinator %@\n%@",

[error localizedDescription], [error userInfo]);

//Present a user facing error

return;

}

ZAssert([fileManager removeItemAtURL:oldURL error:&error],

@"Failed to remove old persistent store at %@\n%@\n%@",

oldURL, [error localizedDescription], [error userInfo]);

if ([self initBlock]) {

dispatch_sync(dispatch_get_main_queue(), ^{

[self initBlock]();

});

}

});

Now we come to the more complicated decision. iCloud is enabled, but we don’t know whether a migration is needed. First, we go ahead and complete the storeURL with the “new” filename, PPRecipes-iCloud.sqlite. Next, we construct the “old” file URL for PPRecipes.sqlite. If the “old” URL exists (via the NSFileManager), then we need to perform a migration.

We add the “old” file to the NSPersistentStoreCoordinator and obtain a reference to the NSPersistentStore. Once we confirm that it was loaded successfully, we can proceed with the migration.

Since we want the “new” store to be connected to iCloud, we now need to add in the options for iCloud configuration to our options dictionary. These are the options we discussed in Configuring iCloud. Once the options dictionary has been updated, we can kick off the migration via a call to ‑migratePersistentStore: toURL: options: withType: error:. This call does several things:

- Creates a new SQLite file at the location specified by storeURL

- Copies all the data from the “old” file to the “new” file

- Registers the “new” file with iCloud per the options specified in the dictionary

- Removes the “old” store from the NSPersistentStoreCoordinator

- Adds the “new” SQLite file to the NSPersistentStoreCoordinator

It’s a lot of work for one line of code, and it should be noted that this line of code can take some time. Therefore, depending on our user experience, we may want to broadcast a notification before the work begins so our user interface updates and lets the user know what’s going on.

Assuming the migration was successful, we now need to delete the old SQLite file from disk so we don’t accidentally repeat these steps on the next launch.

Once the migration and the deletion are complete, we’re finally ready to notify the UIApplicationDelegate that the Core Data stack is ready for use.

Desktop iCloud Integration

So far in this chapter we’ve focused primarily on iOS; the reason is that the desktop implementation is actually a subset of the iOS implementation. There’s no UIManagedDocument on the desktop. As a result, we must use the traditional Core Data stack and add the options to the NSPersistentStoreCoordinator ourselves. Even if we’re using an NSPersistentDocument, we must still handle adding the options for the NSPersistentStoreCoordinator.

To build a new Core Data desktop application and implement iCloud data syncing, refer to Direct NSManagedObjectContext to iCloud for information, because the steps are identical. Further, to migrate an existing desktop application that uses a traditional Core Data stack, refer to Migrating an Existing Application, because that’s also identical.

If we’re using an NSPersistentDocument, things get a little interesting—not a lot but enough to merit attention. The first thing we need to do is to subclass NSPersistentDocument. The reason for this is that unlike with UIManagedDocument, there’s no way to pass in options to the NSPersistentStore when it’s being added to the NSPersistentStoreCoordinator. That’s the reason for subclassing.

DepartmentAndEmployees/MyDocument.m

- (BOOL)configurePersistentStoreCoordinatorForURL:(NSURL*)url

ofType:(NSString*)fileType

modelConfiguration:(NSString*)configuration

storeOptions:(NSDictionary*)storeOptions

error:(NSError**)error

{

NSFileManager *fileManager = [NSFileManager defaultManager];

NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];

if (cloudURL) {

NSString *pathComponent = [url lastPathComponent];

cloudURL = [cloudURL URLByAppendingPathComponent:pathComponent];

NSMutableDictionary *mutableOptions = [storeOptions mutableCopy];

[mutableOptions setValue:[[NSBundle mainBundle] bundleIdentifier]

forKey:NSPersistentStoreUbiquitousContentNameKey];

[mutableOptions setValue:cloudURL

forKey:NSPersistentStoreUbiquitousContentURLKey];

storeOptions = mutableOptions;

}

return [super configurePersistentStoreCoordinatorForURL:url

ofType:fileType

modelConfiguration:configuration

storeOptions:storeOptions

error:error];

}

In this override, we first check to see whether iCloud is enabled. If it is, we build our full iCloud URL and then add the two options required to link our persistent store with iCloud. Once the options dictionary has been updated, we return control to our super’s implementation.

In this example, we take the ‑lastPathComponent from the URL and use it as our unique sandbox within iCloud. Depending on the application’s design, this may not be unique enough, so we might want to consider storing a GUID inside the metadata of the NSPersistentStore and using that as the unique identifier within iCloud.

Once the initialization has been completed and linked to iCloud, all of the other behavior is the same between iOS and Mac OS X. We can listen for notifications about iCloud merging data into Core Data and handle those merges in the same way.

Issues with Data Quantities

As discussed in Under the Hood, Core Data uses transaction logs to keep multiple persistent stores in sync with each other. Because of that design and because of the latency of networking, there’s an upper limit to the frequency with which we can create entities and save them to our Core Data application. The exact numbers are difficult to determine, but it’s safe to say that if we’re generating hundreds of entities per save, we may run into a performance problem.

Whenever we create an NSManagedObject and save the NSManagedObjectContext or the UIManagedDocument, a transaction log is created for that instance. The more entities we create, the larger that transaction log becomes. There’s an upper threshold whereby the creation/transmission of transaction logs is unable to keep up with the frequency of entities being generated. When that threshold is hit, iCloud syncing is unable to keep up with the data generation and eventually fails. This failure usually results in a crash in your application.

Unfortunately, there’s no magic number of entities to keep under. The threshold is a combination of processor speed, number of entities, size of the entities, and network speed. The slower the processor and/or network, then the fewer entities that are needed to reach the threshold. As an example, using an iPhone 4S on a performant Wi-Fi connection, it was possible to reach this threshold by generating a new entity every second with minimal size. With larger entities or a poorer network, it would be possible to reach the threshold with fewer entities.

At this time, there’s no known workaround for this issue other than to decrease the amount of data that’s being pushed to iCloud. The amount of data can be decreased by generating less data or by “rolling up” the data into fewer entities. Ideally, this issue will be resolved at some point soon.

Sharing Data Between iOS and OS X

So far, we have discussed sharing data between iOS devices and linking our OS X application to iCloud. However, we haven’t discussed how to share data between OS X and iOS. Fortunately, there’s virtually no difference between OS X and iOS iCloud integration, though there are a couple of rules that we need to follow.

The Content Name Key Must Be the Same

The NSPersistentStoreUbiquitousContentNameKey is the unique value that iCloud uses to determine what data we’re accessing. If two devices use the same value and are signed by the same developer, they’ll be able to access the same data. If one or more devices and one or more OS X machines use the same key, they’ll share the same data.

Throughout these examples, we’ve been using the value [[NSBundle mainBundle] bundleIdentifier]. There’s nothing wrong with using that value, as long as all of our applications are using the same bundle identifier. But, if they aren’t, then we must use a different string to serve as this key. Apple recommends a reverse DNS notation style, and I certainly see no reason to suggest otherwise.

The Data Model Must Be the Same

Core Data is virtually identical between iOS and OS X. We can use the same data model, even the same file, between OS X and iOS. iCloud expects and requires us to do exactly that. If the data models don’t match, the transaction logs can’t be played back properly, and the synchronizing fails. The easiest way to ensure this is to share the data model (and the entity subclasses) between the OS X application and the iOS application. This step guarantees the applications are using the same model.

However, additional care must be taken when upgrading the model. If we release an update to our iOS application but not our OS X application, they can become out of sync, and iCloud integration will stop working. Once the first device with a new model touches iCloud, iCloud works only with devices that are using the updated model. Any device using the older model simply stops working with iCloud. Fortunately, as soon as the out-of-date machine updates to the latest model, iCloud starts working again.

The best way to test iCloud syncing is to have more than one application running at the same time that uses the data. We could run our existing application on an iPhone and an iPad to see the syncing work, but watching the data sync between an iOS device and an OS X machine is far more interesting. To do that, we must build an OS X client, which is what we’re going to do in Chapter 9, Adding a Desktop Foundation.

Wrapping Up

iCloud is an incredibly powerful feature of iOS/OS X and Core Data. Although it’s not perfect yet, it’s going to continue to improve iteratively. It’s usable in its present form on iOS 6.0 and OS X 10.8. As users get more comfortable using mobile devices, they’re going to expect applications to sync across them.

iCloud is the solution to that problem.

Up to this point in the book we’ve been focusing on iOS since it’s the dominant platform that Core Data runs on. However, Core Data was originally designed to run on OS X and the bulk of what we’ve covered so far works exactly the same on OS X. But, a few things are different, and we’ll explore those next.

Copyright © 2016, The Pragmatic Bookshelf.

Chapter 9

Adding a Desktop Foundation

Until now, we’ve focused primarily on the iOS side of Core Data. Although nearly everything we’ve covered works the same on Mac OS X as it does on iOS, there are some differences. In this chapter, we’re going to look a little more closely at the Mac OS X side of things.

To examine Core Data on Mac OS X, we’ll follow our familiar pattern, starting off with an application on which to base our examples—another foundation. Since we’ve already created an application on iOS that can share its data through iCloud, it seems only fitting to develop a desktop counterpart to the application that’s able to sync.

Our Application

Before we start building our application, here’s a quick overview of how the UI will look and work. Let’s look at our breakdown.

First, in section 1, we want to allow the user to edit information about individual recipes. The user will be able to select a recipe in the list and edit its accompanying details.

In section 2, we will allow the user to enter the ingredients of the selected recipe. Each recipe will have its own list of ingredients that can be added, viewed, and edited here.

And finally, in section 3, we’ll allow the user to add a picture of the recipe for reference. This is a view-only element, and the addition of the image will be handled through the main menu.

Our Application Design

For our revised desktop application, we’re going to start at the very beginning. We’ll launch Xcode and proceed through the steps to create the application and bring it to a usable state. At the end of this chapter, you may be surprised that so few steps are needed to create our Mac OS X application. This ease and efficiency are part of the allure and strength of Cocoa development. Coupled with Core Data, the efficiency is doubled. While we may be used to fairly quick development on iOS, Mac OS X is still easier and quicker to build for—at least up to the prototype stage. Once we have our prototype built and have confirmed that we can do what we want with the application, then all of the “little” things start to become obvious. This is often, lovingly, referred to as “the second 80 percent.”

Sharing the Data Model

Since we have already developed our application for iOS, we want to leverage as much of that knowledge as possible. With Core Data, that leveraging is extensive. The xcdatamodel file structure is identical between Mac OS X and iOS. This means we can use the same data model we’ve been using on iOS. Further, since the data model can be shared and reused, we can share and reuse the data objects as well.

As of the writing of this book, Xcode constructs projects so that the .xcodeproj file is above all the other files needed in the project. The purpose behind this setup is so we can more easily structure our projects to share components between iOS and Mac OS X. Therefore, we start our desktop project by creating a new project named Desktop. I suggest creating the new project in a temporary directory, perhaps on your desktop.

Once the new project is created, quit Xcode. Using Finder, move the contents of the new project in with our existing project. We could further clarify things by renaming the projects, but our main goal is to share the data model and the data objects. The final result is shown here:

Once we have our data objects and data model in a position to be shared, reopen the Desktop.xcodeproj project and drag the folder into Xcode, adding it to the desktop project. Now our data model is complete.

Building the Controller Layer

As you may know from experience developing Cocoa apps, Interface Builder is a large part of any project. Now that we’ve built our data model and we have a template ready in Xcode, it’s time to put together the user interface.

There are two things to note before we get into the fun of Interface Builder. First, this isn’t going to be Delicious Library. We’ll be using standard widgets for our application to help keep the non--Core Data code to a minimum. Second, there are a lot of features we could add to this application, but we’re going to hold back. Extra features, although useful, might detract from our current focus of porting the primary functionality from iOS to the desktop. Once we have that new foundation in place, we can start adding features.

The first part of the user interface we’ll work on is the objects in the xib file. As with most applications, we need to add the AppDelegate to the xib so it will be both instantiated on startup and properly linked into the application itself.

Add the AppDelegate to the xib

Depending on the whims of the templates within Xcode, the AppDelegate may already be in the xib file upon opening MainMenu.xib. If it is, great! Move on to the next section. If it’s not, we need to add it. In addition, please note that depending on the version of Xcode that’s running, the application delegate could have the application name prepended to it. If it does, we must substitute that name for any reference to AppDelegate in this context.

To add the AppDelegate to the xib file, follow these steps:

1. Find the NSObject in the Library palette, and drag it to the xib’s window.

2. Click the name of the NSObject. When it’s editable, change it to AppDelegate.

3. Go to the Identity tab on the Inspector palette, and change the class of the object from NSObject to AppDelegate.

4. Right-drag from the application to the AppDelegate object, and select Delegate.

After these steps, the AppDelegate class will be instantiated when our application launches, and the application will send all delegate messages to it.

Adding the NSArrayController Objects to the xib

We want our application to display a list of all the recipes in a single window. To accomplish this, we need to be able to reference the data so it can be displayed. So, let’s add three NSArrayController objects into our xib that reference that data. Our window then references those NSArrayController objects. Once the NSArrayController objects are added and configured, the xib looks like this:

To add an NSArrayController for the recipe entities, follow these steps:

1. Find the NSArrayController object in the library, and drag it to the xib file.

2. Click the name of the NSArrayController. When it is editable, rename it to Recipes. If you have trouble getting the element into edit mode, change the name in the Identity inspector in Interface Builder, and change the Label field in the Document section.

3. On the Attributes tab of the inspector, change the mode from Class to Entity, and change the entity name to Recipe.

4. Make sure the Prepares Content flag is selected.

5. On the Bindings tab of the inspector, bind ManagedObjectContext to the AppDelegate with a model key path of managedObjectContext.

Now that we have the Recipe entity’s NSArrayController built, we need to configure the other two NSArrayController instances, one for the RecipeIngredient entity and one for the Type entity. The type NSArrayController follows the same steps as our Recipe entity, but we need to set the entity name to Type so it will populate with Type objects. Other than that one difference, we follow the previous steps to complete the type’s NSArrayController.

Set the identity of the last NSArrayController, the recipe ingredients’ NSArrayController, to RecipeIngredient. In the Attributes inspector, choose Entity, and set the entity name to RecipeIngredient. Set the bindings as before, with one additional change: on the Bindings tab of the inspector, enable the content set in the controller content and point it at the recipe’s NSArrayController with a controller key of selection and a model key path of ingredients. See the following image.

Now we’re ready to build the NSWindow.

Building the User Interface

Now that we have all the data objects referenced properly, it’s time to build the user interface. Although this interface won’t be winning an Apple Design Award any time soon, it does allow us to view and edit all the data objects in our model. The window we’re building looks like the following image.

Let’s walk through the steps to set it up. For a more detailed review of this process, look at Chapter 10, OS X: Bindings, KVC, and KVO.

Building the Recipe Source List

The first part of the interface we’re building is in the upper-left corner, just below the search field. This view is an NSTableView configured with one column. It has no horizontal scroll bar, but it does have an automatically displaying vertical scroll bar. In addition, it has a hidden header and the highlight set to Source List. The scroll bars are configured in the inspector for the NSScrollView. The number of columns and the highlight option are configured in the NSTableView inspector. Each of the inspectors can be accessed by Control+Shift-clicking (or Shift+right-clicking) the NSTableView and selecting the appropriate view from the list. If the inspector isn’t on the screen, you can display it via the Tools > Inspector menu item.

To bind this table to our recipe’s NSArrayController object, though, we need to dig down a little bit and get ahold of the NSTableColumn so we can tell that column what to display. We could click in the table view until eventually we select the NSTableColumn, but fortunately there’s an easier way. As mentioned, if we Shift+right-click the table, we’re presented with a pop-up listing of all of the views; we can then select the NSTableColumn as shown in the following image.

With the NSTableColumn selected, we open its Bindings tab in the inspector and bind its value to the recipe’s NSArrayController with a controller key of arrangedObjects and a model key path of name. Once this is set, our Recipe entities show up in this table. More importantly, when we click a recipe in this list, the recipe becomes the selection that feeds the rest of the UI.

Let’s add the buttons that will control the creation and removal of Recipe entities. For this step, we drag an NSButton (it doesn’t matter which one) from the library and place it below the Recipe table view. In the button’s Attributes tab, we set its image to NSAddTemplate (a system-level image available for our use), change its style to Round Rect, and remove its title if it has one. In addition, we must select the menu item Layout > Size to Fit to get the button to the perfect size. Once these steps have been taken for the add button, select Edit > Duplicate from the main menu to create a second button, and change the second button’s image to NSRemoveTemplate.

Next, we can “wire up” the buttons under the NSTableView and connect them directly to the recipe’s NSArrayController. The add button will be connected to the ‑add: action, and the remove button will be connected to the ‑remove: action on the recipe’s NSArrayController. These buttons can be connected to their actions by holding down the Control key, clicking the button, and dragging from the selector sent action to the NSArrayController. With these small changes, we can now add and remove recipe entities at will.

Adding the Recipe Details

Now that the source list is in place, it’s time to add the details about the recipe. These details (the name, serves, desc, and type) tie to the now-valid selection controller key on the recipe’s NSArrayController. As a result, when a user clicks in the list, the relevant details of the recipe are selected.

The first two items are text fields, the third and fifth elements are pop-up boxes, and the final element is a text area. With the exception of the pop-up boxes for the type and author, these details are configured in a very similar way. All of them have a value binding associated with the recipe’s NSArrayController object through a controller key of selection and a model key path of name, serves, and desc, as appropriate. One tip with regard to the text area: be sure to turn off the Rich Text setting. When this setting is on, the field expects an NSAttributedString instead of an NSString, which can cause confusion. Additionally, in order to be good citizens, we should drag an NSNumberFormatter to the Serves text field and configure it to allow only whole numbers.

The pop-up boxes are a little more complex. Although each pop-up box is associated with the selected recipe, we need to populate the entire list of recipes with values. The values belong to other entities on the other side of relationships. While we want to, for example, show the selected recipe type, what we really need to display is the name of the recipe type being selected. Fortunately, this is a fairly common use case, and there are built-in tools to handle it. Each pop-up box is designed to be associated with an NSArrayController. And each NSArrayController references the entities we want to appear in the pop-up boxes. Furthermore, we can define each pop-up box to display a specific value from those entities.

We need to set three sections of values as shown in the following image.

- In the Content section, we bind to the type NSArrayController with a Controller Key setting of arrangedObjects. This step instructs the pop-up box to access the type NSArrayController for the objects it’s to work with.

- In the Content Values section, we bind to the type NSArrayController with a Controller Key setting of arrangedObjects. We also want to set the Model Key Path setting to name. This step instructs the pop-up to access the name property for its display value.

- In the Selected Object section, we bind to the recipe’s NSArrayController and use a Controller Key setting of selection. Further, we want to set the Model Key Path setting to type. This instructs the pop-up to do several things. First, it checks the selected recipe for its type relationship and displays the value associated. Second, it updates the selected recipe when a user selects a different value in the Type pop-up list. And finally, it monitors the recipe NSArrayController and updates itself if the user selects a different recipe.

Once we have the Type pop-up box set, we need to configure the Author pop-up box. The setup here is identical to that for the Type pop-up box, except the NSArrayController to use is the author NSArrayController, and the Selected Object Model Key Path setting will be author.

Adding the Ingredients

Now that the recipe section of our UI is complete, it’s time to add ingredients. The ingredients make up the table in the lower-left corner of our window. Fortunately, this part is almost identical to setting up the recipe source list. However, unlike the recipe source list, our NSTableView will have three columns, display its headers and its vertical scroll bar, and hide the horizontal scroll bar. We enter the column headings as Name, Quantity, and Unit of Measure.

Just as we did in the recipe source list, we bind the values for each column in the NSTableView to our recipe ingredients’ NSArrayController using the controller key arrangedObjects and using the appropriate model key paths: name, quantity, and unitOfMeasure. The Quantity column (or, more specifically, the table cell in the Quantity column) should also have an NSNumberFormatter assigned to it so that the quantity is properly formatted for the value that it holds. Once those are configured, we can see the recipe ingredients for the selected recipe. Remember that we configured the recipe ingredients’ NSArrayController to feed off the selected recipe, so we don’t have to do anything extra at this point.

Like in the recipe source list, the add and subtract buttons are configured by binding them to the recipe ingredients’ NSArrayController objects (the ‑add: and ‑remove: methods, respectively). And with that, the ingredients section is complete, and we’re nearly done with our UI.

Adding a Splash of Code

Wondering where the code is? As it stands, our recipe application is fully functional. We can run it without any actual code on our part and start inputting recipes immediately. The combination of Cocoa and Core Data makes it possible for us to produce this application with no custom code. However, we aren’t stopping there.

Displaying a Picture of the Recipe

Since our iOS counterpart is capable of taking and displaying pictures, it seems only fair that the desktop variant should be able to add and display images. Fortunately, from the UI point of view, this functionality is an easy addition. Drag an NSImageView (aka Image Well) onto our window, and connect its Value Path setting to the imagePath of the recipe’s NSArrayController with a controller key of selection.

Importing Images

Once we add the NSImageView to our user interface, we need to make our AppDelegate aware of it. In addition, we need to add a way to set the image path of our Recipe entities. Therefore, we must update our AppDelegate.h and add an IBOutlet for the recipe NSArrayController and an IBAction to be able to set the image path, as shown here:

Shared/Desktop/AppDelegate.h

#import <Cocoa/Cocoa.h>

#import "PPRDataController.h"

@interface AppDelegate : NSObject

@property (strong) IBOutlet NSWindow *window;

@property (strong) IBOutlet NSImageView *imageView;

@property (strong) IBOutlet NSArrayController *recipeArrayController;

@property (strong) PPRDataController *dataController;

- (IBAction)saveAction:sender;

- (IBAction)addImage:(id)sender;

@end

The IBAction, specifically ‑(IBAction)addImage:(id)sender;, is called from our main menu and displays an open file dialog box. Along with this step, we need a reference to the selected recipe in order to work with the recipe entities. To accomplish this, we add a reference to the recipe’s NSArrayController that’s instantiated in our nib within the AppDelegate.

Once the recipe’s NSArrayController has been added to the AppDelegate header, we need to go back to Interface Builder briefly and Control+drag from the AppDelegate to the recipe’s NSArrayController to complete the binding.

While we’re here, let’s add a menu item to the File menu that will allow the user to add an image for the recipe. We do this by making sure the MainMenu element is open in Interface Builder (it appears as a floating menu) and clicking its File menu. Next, we can either add a new NSMenuItem or use one that already exists that’s not being used. Since the Save As menu item isn’t relevant to our application, let’s go ahead and rename it Add Recipe Image. Once it’s renamed, Control+drag from it to the AppDelegate, and bind the menu item to the IBAction we defined in the header, as shown here:

Shared/Desktop/AppDelegate.m

- (IBAction)addImage:(id)sender;

{

NSManagedObject *recipe = nil;

recipe = [[self.recipeArrayController selectedObjects] lastObject];

if (!recipe) return;

NSOpenPanel *openPanel = [NSOpenPanel openPanel];

[openPanel setCanChooseDirectories:NO];

[openPanel setCanCreateDirectories:NO];

[openPanel setAllowsMultipleSelection:NO];

With the bindings in place, it’s time to implement the ‑addImage: method.

The implementation of ‑addImage: displays an NSOpenPanel, which attaches to the window as a sheet, making it modal to the window. Next, we tweak the NSOpenPanel a little bit so it can’t select directories or multiple files or create directories. Notice that we check to make sure a recipe has been selected before we open the panel. Otherwise, without a recipe, we’d have nothing to associate the image with. A little bit of error checking can go a long way.

Since sheets work asynchronously, we need to add a completion block. The completion block will be called once the user is done interacting with the NSOpenPanel.

Shared/Desktop/AppDelegate.m

[openPanel beginSheetModalForWindow:self.window

completionHandler:^(NSInteger result) {

if (result == NSFileHandlingPanelCancelButton) return;

NSURL *fileURL = [[openPanel URLs] lastObject];

NSError *error = nil;

//Build the path we want the file to be at

NSFileManager *fileManager = [NSFileManager defaultManager];

NSArray *directories = nil;

directories = [fileManager URLsForDirectory:NSDocumentDirectory

inDomains:NSUserDomainMask];

NSURL *destURL = [directories lastObject];

NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString];

destURL = [destURL URLByAppendingPathComponent:guid];

BOOL success = [[NSFileManager defaultManager] copyItemAtURL:fileURL

toURL:destURL

error:&error];

NSAssert2(success, @"Error copying file: %@\n%@",

[error localizedDescription], [error userInfo]);

[recipe setValue:[destURL path] forKey:@"imagePath"];

}];

Our first action in the completion block is to check whether the user canceled out of the panel. An NSInteger is passed in expressly for this reason. If the user did click Cancel, we simply return from the block, and no action is taken.

Once we know the user has selected an image file, it’s time to bring it into our application. We know NSOpenPanel is configured to allow the user to select only one file, so we can grab the ‑lastObject from the ‑URLs method of the NSOpenPanel. We now know what image the user wants added. At this point, we could simply add the path to our recipe.

But what happens if the user moves the image? Or the image is intended to be temporary? To ensure the image is always available, let’s copy it to a known location within our control and use that file path. To accomplish these steps, we grab the filename from the NSOpenPanel and construct a unique path within our Application Support directory structure. Next, we use the NSFileManager to copy the image to that location. Last, we set the new file path into our Recipe object.

With the addition of the image menu item, we’ve completed the initial functionality of our desktop application. We can now share our data model across iOS and OS X. This level of code reuse allows us to maintain both applications and share functionality with a minimum of effort. As we add functionality to one, we merely need to create the UI portion for the other.

Wrapping Up

While the main purpose of this chapter was to set up a desktop app, it also demonstrated how we can share code and data models between the platforms. When it comes to building applications that need to span the platforms, using Core Data reduces the complexity just that much more.

Coming up in the next chapter, we’ll take a quick detour into the powerful features of Objective-C that allowed us to build this desktop application with virtually no code. In addition, we’ll add iCloud syncing and some other features that are unique to the desktop.

Copyright © 2016, The Pragmatic Bookshelf.

Chapter 10

OS X: Bindings, KVC, and KVO

Cocoa Bindings provides a lot of the magic behind Core Data. It consists of a number of APIs and concepts that together allow us to develop our applications using the Model-View-Controller paradigm, without requiring a tight coupling of the three aspects.

Cocoa Bindings allows us to design views, controllers, and models that all expect data in a specific format, without requiring that we bind them to specific classes. This means we can use views in multiple places and swap out controllers and even models without the need for extensive recoding, if any.

In this chapter, we look at some of the key components of Cocoa Bindings and then delve into the specifics of how Core Data works with those bindings. The two primary APIs we discuss are Key Value Coding and Key Value Observing. These APIs are part of the foundation that allows Interface Builder to function. Between the two of them, they give us a tremendous amount of flexibility in our design. In addition, Core Data uses these APIs heavily in order to allow us to focus on the business logic of our applications, as opposed to the data layer. With the combination of Cocoa Bindings and Core Data, the amount of code that we need to write, and therefore debug and maintain, is drastically reduced.

While this chapter is primarily focused on OS X, there are some fundamental portions that affect both OS X and iOS. Specifically, KVC and KVO are available on both platforms. While the Cocoa Bindings discussed in depth in this chapter applies only on OS X, I strongly recommend that iOS developers become familiar with these technologies as well.

Key Value Coding

Key Value Coding (KVC) is one of the cornerstones of Cocoa Bindings. KVC allows us to access the attributes of an object without calling the accessors of that object directly. Key Value Coding is implemented through an informal protocol on NSObject itself and is used mainly through the getter/setter pair ‑valueForKey: and ‑setValue:forKey:.

-valueForKey:

The method ‑valueForKey: is a generic accessor that retrieves an attribute on an object. For example, if we had an object called Recipe and it had an attribute called name, normally we’d access that attribute via the following:

Recipe *myRecipe = ...

NSString *recipeName = [myRecipe name];

However, this requires specific knowledge about the Recipe object to exist in the calling method and generally requires that we import the header file of the Recipe object. However, with Key Value Coding, we can obtain this same attribute without any preexisting knowledge about the Recipe object.

id myRecipe = ...

NSString *recipeName = [myRecipe valueForKey:@"name"];

By itself, this isn’t all that useful. However, there are huge benefits that aren’t obvious on the surface. Here’s an example of how you might better take advantage of this ability:

- (NSString*)description

{

NSMutableString *string = [NSMutableString stringWithFormat:@"[%@] {",

[self class]];

NSEntityDescription *desc = [self entity];

for (NSString *name in [desc attributeKeys]) {

[string appendFormat:@"\n\t%@ = '%@'", name, [self valueForKey:name]];

}

[string appendString:@"\n}"];

return string;

}

In this example, we utilize the NSEntityDescription class (discussed in greater detail in Chapter 2, Under the Hood of Core Data) to retrieve the names all of the attributes of an NSManagedObject subclass and generate an NSString for display in the logs. With this method, we can reuse it in every NSManagedObject subclass that we create, rather than having to create a custom ‑description method for each subclass.

There are a couple of things to note in this example. First, the target object isn’t required to have accessor methods for the attribute being queried. If our target object has only an ivar or property for a name, it will still be resolved and retrieved properly. (ivar stands for instance variable, which is different from a static or local variable.) In addition, if the target object has neither an accessor nor an ivar, the target object will still have a chance to respond to the request before an error occurs via the ‑valueForUndefinedKey: method. Lastly, all the properties of an NSManagedObject are queryable via the KVC protocol. What this means is if we have an NSManagedObject defined in our model, we can retrieve an instance of that object and access its properties without having to implement a single line of code in the target object!

-setValue:forKey:

Dynamically accessing properties on an object is a useful skill, but it’s only half of what KVC does. The other half is the ability to dynamically set attributes on an object in much the same manner that we can retrieve them. Normally, we’d change the name attribute on an Recipe object by calling the setter method.

Recipe *myRecipe = ...

[myRecipe setName:@"Yummy Cookies"];

As in the earlier getter accessor, preexisting knowledge of the Recipe object is required in order to use that accessor without compiler warnings. However, with KVC, we can access it in a more dynamic manner.

id myRecipe = ...

[myRecipe setValue:@"Yummy Cookies" forKey:@"name"];

This call attempts to use the setter ‑setName: if it’s available; if it isn’t, the call will look for and use the attribute directly if it’s available, and failing that, it will call ‑setValue:forUndefinedKey: on the target object. The combination of the dynamic getter and the dynamic setter allows us to manipulate objects without having to write accessors and without having to know (or care!) if they exist. This is used to great effect in one of the Core Data recipes to create a preferences singleton object that reads its values from a properties table. See Chapter 12, Dynamic Parameters.

@property

In addition, as of OS X 10.5 Leopard, we have the keyword @property, which allows us to synthesize accessors to attributes on an object. This feature plays very nicely with KVC, and the two can be used together to produce extremely dynamic and flexible code. By utilizing the @property keyword, we can instruct the compiler to generate the ivar, getter and setter accessors that are KVO-compliant. For example, if we had an object with the following header:

@interface MyObject : NSObject

@property (strong) NSString *myString;

@end

Xcode would interpret it the same as the following header:

@interface MyObject : NSObject

{

NSString *myString;

}

- (NSString*)myString;

- (void)setMyString:(NSString*)string;

@end

With the @property keyword the compiler will automatically generate the accessors for us. Thus the resulting implementation would look similar to the following:

#import "MyObject.h"

@implementation MyObject

- (NSString*)myString;

{

return myString;

}

- (void)setMyString:(NSString*)string;

{

@synchronized(self) {

if ([string isEqualToString:myString]) return;

myString = string;

}

}

@end

With ARC (Automatic Reference Counting), the compiler will also handle the memory management of the properties for us. If we’d defined the property as weak, then the compiler wouldn’t retain the value but merely assign it. Likewise, the locking of the ivar is a default option that we could’ve turned off by adding the nonatomic option to the property definition.

When dealing with multiple properties on an object, this can be a great time-saver. There have also been indications that the accessors generated by the compiler are faster than the “normal” accessors that developers write. In addition to generating accessors, the compiler is smart about what it implements. If we need to implement our own setter for a property, it won’t overwrite that setter.

If we wanted to have the accessors be different than the name of the underlying ivar, we can add the @synthesize keyword to the implementation file:

@implementation MyObject

@synthesize myString = _myString;

@end

The @synthesize keyword was required in previous generations of the compiler, but it’s now optional and only used when the ivar needs to be different than the accessors.

Alongside the @synthesize property, we also have the @dynamic property. The @dynamic property tells the compiler that while the accessors for the property aren’t there at compile time, they will be at runtime, and it instructs the compiler not to produce a warning for them. @synthesize and @dynamic are sibling keywords. For each property, we can use one or the other but not both. However, neither is required in a situation where we’re implementing the accessors ourselves. If the accessor methods will be implemented at runtime, we’d use the @dynamic property instead of the @synthesize property so that the compiler doesn’t produce a warning. This is particularly useful for Core Data subclasses.

Key Value Observing

Key Value Observing (KVO) is the sister API to KVC. KVO allows us to request notifications when an attribute has changed. By observing attributes on an object, we can react when those attributes are changed. KVO is also implemented via an informal protocol on the NSObject, and we register and remove observers using ‑addObserver:for-KeyPath:options:context: and ‑removeObserver:forKeyPath:. These are the two primary methods, although there are other methods involved in the protocol, just as with KVC.

If we wanted to observe the name value on a recipe, we’d add ourselves (or another object) as an observer for that value, like so:

static NSString *kPragProgObserver = @"PragProgObserver"

id myRecipe = ...

[myRecipe addObserver:self

forKeyPath:@"name"

options:NSKeyValueObservingOptionNew

context:kPragProgObserver];

This snippet of code adds self as an observer to the myRecipe object and requests that when the name value changes, to notify self of that change and include both the old value and the new value in that notification. We pass along a context so we can ensure we’re acting on observations meant only for us and that they aren’t accidentally intercepted.

We do this because it’s possible that our code isn’t the only code in our application observing a value, and this method may be called with the intention of being received by our superclass. To ensure that the notification we receive is in fact intended for us, we check the context that’s passed in. After this code has been called, any time the name property is changed on that instance of Recipe, the ‑observeValueForKeyPath:ofObject:change:context: is called on self. We can then handle the change notification as appropriate.

- (void)observeValueForKeyPath:(NSString*)keyPath

ofObject:(id)object

change:(NSDictionary*)change

context:(void*)context

{

if (context != kPragProgObserver) {

[super observeValueForKeyPath:keyPath

ofObject:object

change:change

context:context];

return;

}

NSLog(@"Attribute %@ changed from %@ to %@", keyPath,

[change valueForKey:NSKeyValueChangeOldKey],

[change valueForKey:NSKeyValueChangeNewKey]);

}

When the variable is changed, we see output similar to the following:

Attribute name changed from untitled to Beef Chili

When we’re done observing a value, we can stop receiving messages by passing ‑removeObserver:forKeyPath: to the observed object.

id myRecipe = ...

[myRecipe removeObserver:self

forKeyPath:@"name"];

KVO allows views to automatically refresh themselves from the model when the data has changed. When a view is initialized, it uses KVO to connect all its components to the underlying objects and then uses the notifications to refresh itself.

Cocoa Bindings and Core Data

The combination of KVO/KVC (collectively referred to as Cocoa Bindings) and Core Data reduces the amount of code that we’re required to write by a considerable amount. In the previous chapter, we wrote almost no code to create and display our recipe objects. Nearly all the work that we did was in Interface Builder. In this section, we discuss each of the interface objects that we used and how they work with Core Data.

How does this apply to our application? Let‘s review the user interface that we built in Chapter 9, Adding a Desktop Foundation and how we used KVO and KVC.

NSTableView

Our recipe application makes heavy use of the NSTableView. In the main window of our application, we have two table views: one to list all of the recipes and another to list the ingredients for those recipes. Whenever an application needs to display a list of items or a grid of data, the NSTableView is the element to use.

In an NSTableView, like in the NSOutlineView, we don’t actually bind the table itself to the NSArrayController. Instead, we select each column individually and bind it to a property of the objects in the NSArrayController as shown here.

As we did in Chapter 9, Adding a Desktop Foundation, we bind the column to the arrangedObjects controller key and the model key path to the value we want displayed in that column, as shown in the following image.

NSTableView, like NSOutlineView (as discussed in NSOutlineView), plays very nicely with Core Data. This is especially true when the NSTableView is backed by an NSArrayController that’s feeding the data. It is possible to use NSTableView with a custom data source, if that’s appropriate for the problem at hand. However, when bound with an NSArrayController, the NSTableView can be manipulated with other objects, such as the NSSearchView (discussed in a moment), to produce interfaces that integrate smoothly and provide a great user experience.

With this configuration, the NSTableView displays the data from the NSArrayController automatically and, thanks to KVO, stays in sync with the data stored in the persistence layer.

NSArrayController

NSArrayController is an extremely useful object when working with Core Data because it’s aware of the Core Data layer and knows how to talk to it without any additional code on our part. When we configure it within Interface Builder, all that we really need to give it is the NSManagedObjectContext and a data object type. The rest of the work—retrieving the objects, updating those objects, and creating new ones—is all handled for us.

NSArrayController also understands relationships between objects when it is working with Core Data. In our recipe application, we have one NSArrayController configured to manage RecipeIngredient objects. Based on our data model, these are child objects that are bound to a specific recipe. Because NSArrayController understands these relationships, we can configure it to pull and display only those RecipeIngredient objects that are connected to a Recipe object that’s selected in another NSArrayController.

This again is made possible by KVC and KVO. When we configure the RecipeIngredient’s NSArrayController to provide only those ingredients that are related to the specific recipe, behind the scenes it’s accessing the Recipe object and requesting its ingredients property via KVC. In the RecipeIngredient’s NSArrayController, we bind the NSManagedObjectContext so that new ingredients can be added. In addition to properly being able to create (and remove) objects from the persistence layer, the NSArrayController will manage the relationship between the newly created or removed RecipeIngredient and the parent Recipe object.

All of this works because Core Data is the entire persistence layer and is accessed in a consistent way no matter what object is being dealt with. Because Core Data uses KVO and KVC, our controller objects don’t need to know very much about the objects, other than the name of the objects and where they’re stored. The rest is all resolved at runtime based on the settings that we provide in Interface Builder.

In our recipe application, we have one NSArrayController that’s bound to the Recipe entity in Core Data. Because we also bound that NSArrayController to our NSManagedObjectContext, it’s able to retrieve those Recipe entities automatically and make them available to the rest of the user interface. When our interface is loaded, those NSArrayController objects go out to that NSManagedObjectContext and ask for the entities that currently exist. Once they’re loaded into the NSArrayController objects, any view element associated with them will be notified, via KVO, that the data is available for display. All of this happens behind the scenes; we’re not required to write code for any of it.

NSFormatter

Users expect fields in the interface to accept their input and format it appropriately. This is where NSFormatter objects come into play. When dealing with any type of number, it’s best to add an NSNumberFormatter to the text field or table column and define its display. Likewise, when working with dates, use an NSDateFormatter on the field or column to ensure the data is formatted and validated correctly before it’s stored in the Core Data repository. When working with Core Data, it’s sometimes necessary to manipulate the display of the data so the user’s input can be validated and also so it can be displayed in a usable form. For instance, we aren’t creating a very good user experience if we display currency as 3.99 versus $3.99 or display a date in raw seconds.

In our application, we used an NSNumberFormatter to display the quantity in the Ingredients column of our second NSTableView. If we were to add a shopping list to our application, we’d also use NSNumberFormatter objects to display currency and NSDateFormatter objects to show date and time information.

To add an NSFormatter to a field (either a column or a text field), select it in the Library palette and drag it onto the interface element. Once it’s in place, we can configure it in the Attributes inspector, as shown here. The Attributes inspector allows us to configure exactly how the data is presented to the user.

In addition to properly displaying number and date data, the NSFormatter classes accept input from the user and send that input back to the model in the correct format. For example, by applying NSNumberFormatter to the Quantity column of the ingredients table, we’re guaranteed to receive an NSNumber back from the user interface.

Once an NSFormatter has been applied to an object, it can be tricky to reference it again to make changes. To change or remove an NSFormatter once it’s been applied, select the number formatter in the element list on the left side of the Interface Builder view. Selecting the line item references the NSFormatter again so that it can be manipulated. See the screenshot.

Other Interface Elements That Use KVO, KVC, and Core Data

Although the previous sections show how to access the Core Data repository in our recipe application with KVO and KVC, let’s quickly review the other elements that, if we wanted or needed to, could be utilized to display the data in our application as well.

NSObjectController

NSObjectController shares a lot of similarities with the NSArrayController discussed earlier. However, unlike NSArrayController, NSObjectController is designed to represent one instance rather than an array of instances. A common usage of the NSObjectController is to represent the selected object of an NSArrayController, thereby making it clearer as to what data is being displayed in the interface elements that are bound to the NSObjectController, as opposed to an NSArrayController. Another common usage is to have an entire interface, such as a detail sheet or child window, be bound to the values within an NSObjectController and then have the File’s Owner populate that NSObjectController. In this design, the File’s Owner (usually a subclass of NSWindowController) simply has to populate the NSObjectController with a call to ‑setContent:, and the entire UI is automatically populated. This again makes the maintenance of the code very easy and also improves readability.

NSOutlineView

If we wanted to change the look of our application, we could display a single NSOutlineView instead of the two table views we’re currently using. With an NSOutlineView, we could display a list of recipes with a hierarchy of ingredients listed under them, as depicted in the follwing image.

NSOutlineView shares a lot in common with the NSTableView object. In fact, it’s a subclass of NSTableView. The major difference is that the NSOutlineView displays data in both a column format as well as a hierarchal format. This changes how the data needs to be represented and accessed. Instead of a flat array of objects, the NSOutlineView expects the data to be in a tree structure. Fortunately, there’s a controller designed just for that use: NSTreeController. Some care must be taken when working with Core Data and an NSOutlineView. In general, the NSOutlineView and the NSTreeController expect the data to be in a fairly organized state. NSTreeController expects each parent (or branch) to have children accessible via the same methods. This is a bit counterintuitive to having descriptive names for relationships between objects, and I normally implement accessors instead of making my relationships generic. For instance, if we had a recipe that has children named RecipeIngredients, I’d add another accessor to that relationship called children, purely for the NSOutlineView to use. We discuss custom NSManagedObject classes in Chapter 2, Under the Hood of Core Data.

Unlike its parent object, NSTableView, the NSOutlineView doesn’t work as cleanly as we might expect. We can combine it with the NSTreeController, but we get a lot more functionality and control by implementing the data source protocol for the NSOutlineView instead of using the NSTreeController object.

NSTreeController

As discussed in NSOutlineView, NSTreeController objects are primarily used by the NSOutlineView interface element. Although they can store any data that lends itself to a tree structure, they’re best suited as a controller for NSOutlineView objects. Unfortunately, there is still quite a bit of work to be done with the NSTreeController, and the results we get from working with it can be unexpected and unclear. I recommend skipping it at this time and implementing the data source protocol instead when working with tree data.

NSSearchField

The NSSearchField interface element is an extremely useful tool and can provide an extra bit of polish to an interface. Its primary purpose is to filter the objects in an NSArrayController. That may not seem like much, until we remember that, thanks to KVO, any tables or interface elements associated with that NSArrayController will get updated automatically and instantly. This means if we put a search field into our application and link it to our NSArrayController of Recipe objects, our source list of recipes will automatically be filtered based on the user input into that NSSearchField. Even better, we don’t have to write any code! All we need to do to implement it is configure the bindings for the NSSearchField.

To accomplish this, we first add an NSSearchField to our application. Here, we’ve decreased the vertical size of the recipe source list and inserted an NSSearchField above it. Next, we configure its bindings.

As shown in the following image, the NSSearchField interface element works with an NSPredicate. We write the predicate in the Predicate Format field, substituting $value for whatever the user inputs into the search field and using the controller key and value transformer to bind it to our data. In this example, we want to filter on the name of recipes; therefore, we bind the NSSearchField to our recipe’s NSArrayController using the controller key of filterPredicate and a predicate of name contains[c] $value.

Once we add one predicate, another appears on the Bindings tab for the NSSearchField. This is so we can use a search field for more than one type of search. Each search will be shown in the drop-down on the NSSearchField, and the Display Name binding will be shown to the user. This allows us to create one NSSearchField that can search for recipe names, ingredients, descriptions, or anything else we may need.

Once the binding is complete, we’re done adding a basic search field. Running the application shows that text entered into the search field impacts the list of recipes, as shown here.

Wrapping Up

Now that we have an understanding of Cocoa Bindings, KVO, and KVC, what else can we add to our desktop application that’s specific to the desktop? In the next chapter, we’ll look at additions to our application that will be used outside of our application.

Copyright © 2016, The Pragmatic Bookshelf.

Chapter 11

Spotlight, Quick Look, and Core Data

Developing for Mac OS X is about functionality meeting quality. When our applications have one without the other, we leave our users wanting more. They may not be able to define it, but “something” will be missing. Spotlight and Quick Look integration are two of those things that users don’t look for when trying a new application but are pleasantly surprised by when they stumble upon them. Surprisingly, not a lot of developers handle this integration. Perhaps it’s because Spotlight doesn’t get along with Core Data very well. Perhaps the feature is too abstracted away. But one thing is for certain—integrating with Spotlight is the right move going forward. Spotlight is here to stay, and users will be using it more often and in more creative ways.

Unfortunately, for technical reasons, Spotlight and Core Data are at odds with each other. Spotlight works on the metadata of individual files, and Core Data stores everything in a single file. Because Spotlight is designed to work with the metadata of a file to discover things about the file, it won’t work very well with a single file design such as Core Data. When Tiger was first released, there were a number of applications (such as Entourage) that, because of their single file design, did not play nicely with Spotlight. In fact, Apple re-architected Mail for that reason.

In this chapter, we’ll integrate Spotlight into our recipes application. Once we’re done, our users will be able to search for Pot Roast and find it in our application. Also, when they select that search result, our application will not only launch but open to Pot Roast. While we’re solving the Spotlight issue, we’re also going to glance at Quick Look. On the surface these two technologies appear to be completely different, but they’re handled in a similar fashion by Mac OS X and the Finder. And although it’s not 100 percent appropriate for our sample application (since we have only a single data file and that data file is hidden away in the Library/Application Support directory), it’s very useful to understand how Quick Look works for document-based Core Data applications because it makes them easier to find in Finder, Spotlight, Time Machine, Mail, and other applications. Lastly, Quick Look and Spotlight integrate rather well together. If our users activate Quick Look on a Spotlight result, we want them to see information about the recipe, not a picture of a generic file.

Should I Just Use Separate Files?

Throughout this book, the application we’re designing uses a single Core Data file. This is done for the sake of clarity and focus on Core Data. Depending on the application that’s being designed, it’s highly likely that it will be document based, and therefore it would be appropriate to have one Core Data repository per document. In that situation, Spotlight and Quick Look can be a lot easier to integrate.

However, for applications that aren’t document based, it’s preferable to use a single Core Data repository, as opposed to individual files. Although individual files make Spotlight easier to work with, they would be the tail wagging the dog. The main focus of object persistence (in other words, data files) is to quickly and easily access the data in a logical and reproducible manner. Core Data solves all those problems quite neatly—with the unfortunate wrinkle of not being fully compatible with Spotlight.

Integrating with Spotlight

The issue, as mentioned, is one of multiple files. Ideally, for our recipe application, we want one Spotlight “record” for each recipe in our Core Data repository. For Spotlight to work properly, we’d need one file on the disk for each recipe, along with its associated metadata. Therefore, to make Spotlight happy, we’ll do exactly that. However, since all our data is being stored in a Core Data repository, there’s no reason to store any data in these files. These additional files exist purely for Spotlight (and Quick Look) to utilize. Since Spotlight doesn’t need any data in the files to work (it just needs metadata), we’ll create very simple files and link them back to our Core Data repository.

The other gotcha with Spotlight is that the importer needs to be as fast as possible. What might be acceptable for processing one file or ten files isn’t going to fly when Spotlight has to chug through thousands of files. Since the same importer that we’re writing for use inside our application could potentially be used in a server situation, it needs to be as fast as we can make it. So, we’re going to cheat a bit. Instead of looking up the metadata in our Core Data repository upon request from Spotlight, we’ll store the metadata in the files we’re creating for Spotlight. That way, our importer has to touch the metadata files only and doesn’t need to initialize the entire Core Data “stack” (that is, NSManagedObjectContext, NSPersistentStoreCoordinator, and NSManagedObjectModel).

Creating the Metadata Files

We first need to produce and update the metadata files on the fly. To keep them as simple as possible, we just use plist files, as opposed to a binary representation or some other format. Since NSDictionary understands plist files, it reduces the amount of overhead needed for loading and saving the files.

To begin, let’s create our first NSManagedObject subclass. This subclass handles producing the NSDictionary that will contain all the metadata. Since we’re creating a subclass, we might as well implement some of the properties we’ll be using to reduce the code complexity and make it easier to maintain.

Therefore, our header file looks as follows:

Spotlight/PPRecipe.h

#import <Cocoa/Cocoa.h>

#define kPPImagePath @"kPPImagePath"

#define kPPObjectID @"kPPObjectID"

#define kPPServes @"kPPServes"

#define kPPItemTitle (id)kMDItemTitle

#define kPPItemTextContent (id)kMDItemTextContent

#define kPPItemAuthors (id)kMDItemAuthors

#define kPPItemLastUsedDate (id)kMDItemLastUsedDate

#define kPPItemDisplayName (id)kMDItemDisplayName

@interface PPRecipe : NSManagedObject

@property (assign) NSString *desc;

@property (assign) NSString *name;

@property (assign) NSString *type;

@property (assign) NSManagedObject *author;

@property (assign) NSDate *lastUsed;

- (NSDictionary*)metadata;

- (NSString*)metadataFilename;

@end

We need to make sure we change the Class setting in the latest data model so Core Data uses our subclass rather than the default NSManagedObject as shown in the screenshot.

Implementing the Metadata Method

The goal of this metadata file is to contain just enough information to populate Spotlight and Quick Look but not so much information that the files become large and cumbersome. We must pretend there will be thousands of these files (even if in reality that would be impractical), and we don’t want to impact the users’ performance or their hard drive capacity. For our metadata files, we really need only the following information:

- The name of the recipe

- The number of people it serves

- The image for the recipe

- The last time it was served

- The description of how to prepare it

Most of that list is very light—just text. However, the image is probably too large to cram into the plist file, especially because we can’t be sure how large that file will be. In addition, it’d complicate the file format by including binary data. Therefore, we’ll put in the path of the image instead of the actual image. Since the image is stored on disk, we just access that copy.

In addition to this list, we need to add one more item that is not user facing. We want a way to link back to the recipe record in our Core Data repository so if the user tries to open the metadata file, instead our application will open and select the correct record. To do this, we use the NSManagedObjectID of the recipe and store its URIRepresentation (which is actually an NSURL) as a string in the metadata.

Spotlight/PPRecipe.m

- (NSDictionary*)metadata;

{

NSMutableDictionary *metadataDict = [NSMutableDictionary dictionary];

metadataDict[kPPItemTitle] = [self name];

metadataDict[kPPItemTextContent] = [self desc];

metadataDict[kPPItemAuthors] = [[self author] valueForKey:@"name"];

metadataDict[kPPImagePath] = [self valueForKey:@"imagePath"];

metadataDict[kPPItemLastUsedDate] = [self lastUsed];

metadataDict[kPPServes] = [self valueForKey:@"serves"];

NSString *temp = [NSString stringWithFormat:@"Recipe: %@", [self name]];

metadataDict[kPPObjectID] = temp;

temp = [[[self objectID] URIRepresentation] absoluteString];

metadataDict[(id)kMDItemTitle] = temp;

return metadataDict;

}

Implementing the metadataName Method

Because we want users to be able to view the actual metadata files in the Finder, the filenames should represent the recipe rather than an abstract name. We use the name attribute of the recipe itself as the filename.

Spotlight/PPRecipe.m

- (NSString*)metadataFilename;

{

return [[self name] stringByAppendingPathExtension:@"grokkingrecipe"];

}

Generating and Updating the Metadata Files

Now that we have an implementation for generating the metadata per recipe, we need to add the ability to populate these files and keep them up-to-date. Ideally, we want to refresh the metadata files every time that the NSManagedObjectContext is saved. To do this, we add a new ‑save: method to our AppDelegate and route all of our saves through it.

Spotlight/AppDelegate.m

- (BOOL)save:(NSError**)error;

{

NSManagedObjectContext *moc = [self managedObjectContext];

if (!moc) return YES;

if (![moc hasChanges]) return YES;

//Grab a reference to all of the objects we will need to work with

NSSet *deleted = [moc deletedObjects];

NSMutableSet *deletedPaths = [NSMutableSet set];

for (NSManagedObject *object in deleted) {

if (![object isKindOfClass:[PPRecipe class]]) continue;

[deletedPaths addObject:[object valueForKey:@"metadataFilename"]];

}

NSMutableSet *updated = [NSMutableSet setWithSet:[moc insertedObjects]];

[updated unionSet:[moc updatedObjects]];

//Save the context

if (![moc save:error]) {

return NO;

}

return [self updateMetadataForObjects:updated

andDeletedObjects:deletedPaths

error:error];

}

In this new ‑save: method, we’re doing a couple of things before calling ‑save: on the NSManagedObjectContext. Because the NSManagedObjectContext knows what objects have been deleted, updated, or inserted, we want to grab a reference to that information before the ‑save: occurs. Once the ‑save: is complete, that information is no longer available. Therefore, we grab a reference to the NSSet of deleted objects, updated objects, and inserted objects. Because the deleted objects will be, well, deleted once the ‑save: is performed, we want to extract the information we care about beforehand. So, we loop over the deleted objects looking for Recipe instances. When we find one, we extract its metadataFilename and store it in a new NSMutableSet. In addition, since we’ll be doing the same thing to the inserted and the updated objects, we merge them into one set. Once we have that information, we go ahead and save the context. If the save fails, we just abort and let the calling code handle the error. When the save is successful, it’s time to update the metadata.

Spotlight/AppDelegate.m

if ((!updatedObjects || ![updatedObjects count]) &&

(!deletedObjects || ![deletedObjects count])) return YES;

NSString *path = [self metadataFolder:error];

if (!path) return NO;

BOOL directory = NO;

NSFileManager *fileManager = [NSFileManager defaultManager];

if (![fileManager fileExistsAtPath:path isDirectory:&directory]) {

if (![fileManager createDirectoryAtPath:path

withIntermediateDirectories:YES

attributes:nil

error:error]) {

return NO;

}

directory = YES;

}

if (!directory) {

NSMutableDictionary *errorDict = [NSMutableDictionary dictionary];

NSString *msg = NSLocalizedString(@"File in place of metadata directory",

@"metadata directory is a file error description");

[errorDict setValue:msg forKey:NSLocalizedDescriptionKey];

*error = [NSError errorWithDomain:@"pragprog"

code:1001

userInfo:errorDict];

return NO;

}

Because we want to be in the habit of assuming nothing, we first check that there’s something to update or delete. Once we’re past that check, we need to confirm that the cache directory is in place, and either our metadata directory is in place or we can create it. If any of this fails, we update the NSError object and return.

Spotlight/AppDelegate.m

NSString *filePath = nil;

if (deletedObjects && [deletedObjects count]) {

for (NSString *filename in deletedObjects) {

filePath = [path stringByAppendingPathComponent:filename];

if (![fileManager fileExistsAtPath:filePath]) continue;

if (![fileManager removeItemAtPath:filePath error:error]) return NO;

}

}

The next part of updating the metadata is to remove any files that are no longer appropriate. Therefore, if the passed-in deletedObjects set contains any objects, we need to loop over it. Since we know that the name of the metadata file is stored in the deletedObjects variable, we append it to the metadata directory path and check for the existence of the file. If it exists, we delete it. (It may be possible that a recipe got created and deleted without ever being saved to disk. It’s unlikely, but why take chances?) If we run into an issue deleting the file, we abort the update and let the calling method handle the error.

Spotlight/AppDelegate.m

if (!updatedObjects || ![updatedObjects count]) return YES;

NSNumber *_YES = [NSNumber numberWithBool:YES];

NSDictionary *attributesDictionary = [NSDictionary

dictionaryWithObject:_YES

forKey:NSFileExtensionHidden];

for (id object in updatedObjects) {

if (![object isKindOfClass:[PPRecipe class]]) continue;

PPRecipe *recipe = object;

NSDictionary *metadata = [recipe metadata];

filePath = [recipe metadataFilename];

filePath = [path stringByAppendingPathComponent:filePath];

[metadata writeToFile:filePath atomically:YES];

NSError *error = nil;

if (![fileManager setAttributes:attributesDictionary

ofItemAtPath:filePath

error:&error]) {

NSLog(@"Failed to set attributes: %@\n%@",

[error localizedDescription], [error userInfo]);

abort();

}

}

return YES;

The last part of updating the metadata files is to process existing or new recipes. As with the deleted objects earlier, we first check to see whether there are any objects to update, and if there aren’t, we’re done. If there are new or updated objects, we again loop through the NSSet looking for PPRecipe entities. For each recipe we find, we request its metadata NSDictionary object from the metadata method we created earlier. Using that NSDictionary along with the metadataFilename method, we write the NSDictionary to disk. For one last bit of polish, we update the attributes on the newly created (or updated) file and tell it to hide its file extension. This gives us the cleanest appearance when viewed inside the Finder.

Now that the ‑save: method has been written, we need to route all the ‑save: calls that exist to call this method instead of calling ‑save: directly on the NSManagedObjectContext. Currently, this requires modifying both the ‑(NSApplicationTerminateReply)applicationShouldTerminate: method and the ‑(IBAction)saveAction: method. In each case, we just need to change the following:

[[self managedObjectContext] save:&error];

to a message to the ‑save: method on the AppDelegate.

[self save:&error];

There’s one last situation we have to handle. If we have existing users and are adding the Spotlight integration after v1.0, we need some way to bring our users up to speed. To do this, we add a check to the ‑(void)applicationDidFinishLaunching: method. If the metadata directory doesn’t exist, we must do a full push of all the metadata in the persistent store.

Spotlight/AppDelegate.m

NSError *error = nil;

NSString *path = [self metadataFolder:&error];

if (!path) {

NSLog(@"%s Error resolving cache path: %@", __PRETTY_FUNCTION__, error);

return;

}

if ([[NSFileManager defaultManager] fileExistsAtPath:path]) return;

NSManagedObjectContext *moc = [self managedObjectContext];

NSFetchRequest *request = nil;

request = [NSFetchRequest fetchRequestWithEntityName:@"Recipe"];

NSSet *recipes = [NSSet setWithArray:[moc executeFetchRequest:request

error:&error]];

if (error) {

NSLog(@"%s Error: %@", __PRETTY_FUNCTION__, error);

return;

}

[self updateMetadataForObjects:recipes andDeletedObjects:nil error:&error];

if (error) {

NSLog(@"%s Error: %@", __PRETTY_FUNCTION__, error);

return;

}

Here we’re looking for the metadata cache directory, and if it doesn’t exist, we fetch every recipe entity in the persistent store and pass the NSSet to our metadata-building method. This also protects us from users who like to periodically delete their cache directory. This method calls the ‑metadataFolder method to determine where the metadata should be stored.

Spotlight/AppDelegate.m

- (NSString*)metadataFolder:(NSError**)error

{

NSString *path = nil;

path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory,

NSUserDomainMask, YES) lastObject];

if (!path) {

NSMutableDictionary *errorDict = [NSMutableDictionary dictionary];

NSString *desc = NSLocalizedString(@"Failed to locate caches directory",

@"caches directory error description")

[errorDict setValue:desc forKey:NSLocalizedDescriptionKey];

*error = [NSError errorWithDomain:@"pragprog"

code:1000

userInfo:errorDict];

return nil;

}

path = [path stringByAppendingPathComponent:@"Metadata"];

path = [path stringByAppendingPathComponent:@"GrokkingRecipes"];

return path;

}

In the ‑metadataFolder, we first request a list of the cache directories from the NSSearchPathForDirectoriesInDomain method and append the path components Metadata and GrokkingRecipes to it. We don’t check to see whether the path exists at this point but instead let our caller decide how to handle that.

Creating the Spotlight Importer

Now that we have some metadata to work with, it’s time to build the Spotlight importer. To start this part of the application, we need to first address UTIs.

Uniform Type Identifiers (UTIs)

Both Spotlight and Quick Look use UTIs rather than filename extensions to connect files on disk with (Spotlight) importers and (Quick Look) generators. A UTI is a unique string that identifies the type of data stored in a given file. It’s recommended that UTIs identify the company and application that created the data file, and like bundle identifiers, a reverse domain name is ideal for this purpose. (It should be noted that bundle identifiers are in fact UTIs themselves.) Since our application uses com.pragprog.grokkingrecipes as its unique bundle identifier, we’ll use the same UTI as the value of the LSItemContentTypes to identify the files.

Spotlight/Info.plist

<key>CFBundleDocumentTypes</key>

<array>

<dict>

<key>CFBundleTypeExtensions</key>

<array>

<string>grokkingrecipe</string>

</array>

<key>CFBundleTypeIconFile</key>

<string>book.icns</string>

<key>CFBundleTypeName</key>

<string>Grokking Recipe</string>

<key>CFBundleTypeRole</key>

<string>Editor</string>

<key>LSItemContentTypes</key>

<array>

<string>com.pragprog.grokkingrecipe</string>

</array>

<key>NSPersistentStoreTypeKey</key>

<string>XML</string>

</dict>

</array>

The UTExportedTypeDeclarations section is probably very familiar. Xcode generates it to describe any file that’s handled by the application being built. The one difference is that, instead of defining a file extension (like txt), we’re defining a UTI that is being handled by our application. Since this UTI is unknown by the system, we need to describe it, again in our Info.plist file.

Spotlight/Info.plist

<key>UTExportedTypeDeclarations</key>

<array>

<dict>

<key>UTTypeConformsTo</key>

<array>

<string>public.data</string>

<string>public.content</string>

</array>

<key>UTTypeDescription</key>

<string>Grokking Recipe</string>

<key>UTTypeIdentifier</key>

<string>com.pragprog.grokkingrecipe</string>

<key>UTTypeTagSpecification</key>

<dict>

<key>public.filename-extension</key>

<string>grokkingrecipe</string>

</dict>

</dict>

</array>

This key describes exporting our UTI and tells Mac OS X how to link it to different file extensions. In addition, this section describes the data to Mac OS X, telling the OS a descriptive name for the data type and where in the UTI tree it fits.[5]

Xcode Subproject

Our Spotlight importer is actually its own application. Xcode handles this with a separate project for the importer. (It’s actually possible to include the plug-in as part of the main application project, but I’ve found that to be more hassle than it’s worth.) Since we want to include the importer as part of our primary application and we don’t want to have to remember to rebuild the subproject every time we build our main project, we’ll set it up as a dependent or subproject within our primary project. To do this, we start with creating a project in Xcode and selecting the Spotlight importer, as shown in the following image.

We want to save this project in a directory inside our primary recipe project, and we don’t want to be too clever. We’ll give the subproject an obvious name like SpotlightPlugin and include it with the Spotlight example project. To make Xcode build this plug-in every time we build the main project, we need to link the two together.

This is accomplished with the following steps:

1. Drag the subproject into the main project (shown in the previous figure).

2. Open the target in the main project, and select the General tab.

3. Add the subproject as a dependency.

4. Add a new copy phase to the main project’s target, and set its destination to wrapper and path to Contents/Library/Spotlight.

5. Drag the Spotlight plug-in into the new build phase (shown next).

Now, whenever we clean or build the main project, the subproject is cleaned/built. Taking this step also allows the subproject to be built with the same settings as the primary project.

Linking the Spotlight Importer to the UTI

With our Spotlight importer subproject in place, it’s time to link the importer to the UTI for our metadata files. To do this, we need to update the Info.plist of our Spotlight subproject to let the operating system know which UTIs this importer handles.

<array>

<dict>

<key>CFBundleTypeRole</key>

<string>MDImporter</string>

<key>LSItemContentTypes</key>

<array>

<string>com.pragprog.grokkingrecipe</string>

</array>

</dict>

</array>

Here, we’re defining our plug-in as having an MDImporter role, and the list of UTIs contains just the one for our metadata file. With this change, Mac OS X knows to use this importer to retrieve the information for our metadata files.

Building the Spotlight Importer

Now that everything is connected, it’s time to build the importer. Fortunately, this is the easiest and shortest part of the entire process. The Spotlight template created the main.m file that we’ll be using, and it contains all the boilerplate code for us. The only code we need to write for the importer is in the GetMetadataForFile.m file. The template generates a GetMetadataForFile.c file, and that file won’t accept any Objective-C code. Since I prefer Objective-C over straight C, the first thing I did was rename the c file to an m file. This tells Xcode to compile it as Objective-C rather than C. Since we’ll be using Foundation APIs, we need to include Foundation.framework as well.

Spotlight/SpotlightPlugin/GetMetadataForFile.m

#include <CoreFoundation/CoreFoundation.h>

#include <CoreServices/CoreServices.h>

#import <Foundation/Foundation.h>

Boolean GetMetadataForFile(void* thisInterface,

CFMutableDictionaryRef attributes,

CFStringRef contentTypeUTI,

CFStringRef pathToFile)

{

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

NSDictionary *meta;

meta = [NSDictionary dictionaryWithContentsOfFile:(NSString*)pathToFile];

for (NSString *key in [meta allKeys]) {

[(id)attributes setObject:[meta objectForKey:key] forKey:key];

}

[pool release], pool = nil;

return TRUE;

}

The actual code for the importer is almost laughably simple. We’re just loading the metadata file back into an NSDictionary, looping over the keys using the allKeys method, and adding each associated value to the passed-in CFMutableDictionaryRef. Once we’re done with the NSDictionary, we return TRUE and we’re done. Since we’re running inside a C function, we need to wrap the entire procedure in an NSAutoreleasePool so that we aren’t leaking any memory.

Testing the Spotlight Importer

There are a couple of ways to test the importer to make sure everything is working properly. The first thing we need to do is generate the metadata files, which we accomplish by running our application. Once the metadata files are created, we can test the importer.

We can get a lot of information about our importer directly on the command line. Mac OS X includes a command-line tool called mdimport. A quick review of the man page reveals there are three switches for this command that are of immediate use. First, we need to tell Spotlight to load our importer.

mdimport -r ${path to our project}/build/Debug/GrokkingRecipes.app/

Contents/Library/Spotlight/SpotlightPlugin.mdimporter

Once Spotlight is aware of our importer, we can start querying it, again from the command line using the mdimport command.

cd ~/Library/Caches/Metadata/GrokkingRecipes

mdimport -d2 Test.grokkingrecipe

We can change the debug level (from 1 to 4) to display different quantities of information about the metadata file. If we use level 2 we can confirm the importer is working and get a basic summary of the data contained inside the file.

The other way to test the importer is to just search for one of our recipes! Click the spotlight magnifying glass in the upper-right corner, and enter the name of one of the recipes, as shown here.

But what happens when we try to open this file?

Accepting Metadata Files

Since we linked our metadata files to the primary application, Mac OS X attempts to open our application and pass the file to us. However, we have no way of handling that yet. We need to teach our application to accept a request to open a file:

Spotlight/AppDelegate.m

- (BOOL)application:(NSApplication*)theApplication

openFile:(NSString*)filename

{

NSDictionary *meta = [NSDictionary dictionaryWithContentsOfFile:filename];

NSString *objectIDString = [meta valueForKey:(id)kPPObjectID];

NSURL *objectURI = [NSURL URLWithString:objectIDString];

NSPersistentStoreCoordinator *coordinator;

coordinator = [[self managedObjectContext] persistentStoreCoordinator];

NSManagedObjectID *oID;

oID = [coordinator managedObjectIDForURIRepresentation:objectURI];

NSManagedObject *recipe = [[self managedObjectContext] objectWithID:oID];

if (!recipe) return NO;

dispatch_async(dispatch_get_main_queue(), ^{

NSArray *array = [NSArray arrayWithObject:recipe];

[[self recipeArrayController] setSelectedObjects:array];

});

return YES;

}

In our application delegate, we need to add the method ‑(BOOL)application:openFile: that will be called when the operating system attempts to open one of our metadata files. In that method, we load the metadata file into an NSDictionary and retrieve the URIRepresentation of the NSManagedObjectID. With the NSManagedObjectID in hand, we can load the represented Recipe entity and display it to the user. Since we want to return from this method as quickly as possible (the operating system is waiting on an answer), we display the recipe after we return from this method.

To do that, we wrap the call to display the recipe in a dispatch_async, which updates the recipeArrayController with the selected recipe and allows the UI to update. By doing a dispatch_async and putting the execution block onto the main queue, we’re effectively telling the OS to run the block right after the current run loop completes.

With that code in place, we can select a recipe from Spotlight, and our application opens with the correct recipe selected. The first part of our OS integration is now in place.

Integrating with Quick Look

There are two different ways to implement Quick Look. The application can generate images as part of the data bundle, or a generator can be written that generates the images on the fly. Storing images with the data is viable only if the data is stored in a bundle similar to the way that Pages or Numbers does. When the data is stored in a flat file, like our metadata files, a generator is the only way to integrate with Quick Look. Fortunately, writing a Quick Look generator is only slightly more complicated than a Spotlight importer.

Just like the Spotlight importer, the Quick Look generator is created within its own subproject.

Like the Spotlight importer subproject we added earlier, we need to perform the following steps:

1. Create a subproject under our recipes project. Again, I gave mine the very clever title of QuickLookPlugin.

2. Drag the project into the main project, and flag it as a dependency.

3. Add a new copy phase to the main project’s target, and set its destination to wrapper and path to Contents/Library/QuickLook.

4. Drag the Quick Look plug-in into the new build phase.

If any of these steps are confusing, please see Xcode Subproject. Once the Quick Look subproject has been added, the main project’s tree should look similar to this:

Now that the subproject has been set up properly, we’ll go ahead and rename the two c files to m files so that we can use Objective-C inside them. We also need to add Foundation.framework to the subproject so that we can utilize the Foundation APIs.

Unlike Spotlight, Quick Look has two components: a thumbnail generation and a preview generation. The thumbnail is used by the Finder both in place of a standard file icon and in Cover Flow. The preview is used when Quick Look is invoked in Finder, Mail, and so on. Therefore, the Quick Look template creates two c (now m) files, one for each. Let’s tackle the thumbnail file first.

Generating the Quick Look Thumbnail

The file GenerateThumbnailForURL.m has one function inside it that’s called by the Quick Look manager (part of the operating system). This function expects we’ll be populating the QLThumbnailRequestRef and returning the OSStatus of noErr. Based on the documentation for Quick Look, even if we suffer a complete failure inside our plug-in, we should always return noErr.

As you can probably guess, our thumbnail generation code is going to be very simple. Since we already have an image included with each recipe, we’re simply going to pass that image back whenever it’s requested.

Spotlight/QuickLookPlugin/GenerateThumbnailForURL.m

OSStatus GenerateThumbnailForURL(void *thisInterface,

QLThumbnailRequestRef thumbnail,

CFURLRef url,

CFStringRef contentTypeUTI,

CFDictionaryRef options,

CGSize maxSize)

{

@autoreleasepool {

NSDictionary *meta;

meta = [NSDictionary dictionaryWithContentsOfURL:(__bridge NSURL*)url];

NSString *pathToImage = [meta valueForKey:@"kPPImagePath"];

if (!pathToImage) {

//No image available

return noErr;

}

NSData *imageData = [NSData dataWithContentsOfFile:pathToImage];

if (!imageData) {

//Unable to load the data for some reason.

return noErr;

}

QLThumbnailRequestSetImageWithData(thumbnail,

(__bridge CFDataRef)imageData, NULL);

}

return noErr;

}

In this method, we’re again retrieving the metadata file and loading it into an NSDictionary. From that dictionary, we’re retrieving the path to the image for the recipe and loading the image into an NSData object. From there, we call the QLThumbnailRequestSetImageWithData(QLThumbnailRequestRef, CFDataRect, CFDictionaryRef) method, which populates the QLThumbnailRequestRef. After that’s done, we pop the NSAutoreleasePool and return noErr. From there, Quick Look uses the image we’ve provided whenever it needs a thumbnail for the file.

Generating the Quick Look Preview

The Quick Look preview is understandably more complex than generating a thumbnail image. If we do absolutely nothing for this part of Quick Look, we’d still get a rather satisfying preview, as shown in the figure. But why stop there when we can do so much more?

Like the thumbnail generator, the preview generator is contained within one function call, and we’re expected to populate the QLPreviewRequestRef and return noErr. Also, like the thumbnail generator, we’ll always return noErr no matter what happens within our function call.

Unlike the thumbnail generator, we aren’t going to be working with just the image for the recipe. Instead, we’ll generate a full HTML page that contains a large amount of information about the recipe and use that as our preview. Although it’d be possible to generate the entire HTML page in code, I’m rather lazy and I’d rather avoid that. Instead, let’s take advantage of some XPath queries to locate the correct nodes inside a template HTML file, change the values to be appropriate for our current recipe, and use that to generate the QLPreviewRequestRef.

Spotlight/QuickLookPlugin/GeneratePreviewForURL.m

NSString *bundleID = @"com.pragprog.quicklook.grokkingrecipe";

OSStatus GeneratePreviewForURL(void *thisInterface,

QLPreviewRequestRef preview,

CFURLRef url,

CFStringRef contentTypeUTI,

CFDictionaryRef options)

{

@autoreleasepool {

NSDictionary *meta;

meta = [NSDictionary dictionaryWithContentsOfURL:(__bridge NSURL*)url];

if (!metadata) return noErr;

NSLog(@"metadata: %@", metadata);

NSString *imagePath = [metadata valueForKey:@"kPPImagePath"];

NSData *imageData = [[NSData alloc] initWithContentsOfFile:imagePath];

if (!imageData) return noErr;

To start with, we load the metadata dictionary as we have previously. We’re also going to load the image data into an NSData object again. Assuming there are no issues with either the metadata or the image loading, the next step is to set up the options for the HTML page.

Spotlight/QuickLookPlugin/GeneratePreviewForURL.m

NSMutableDictionary *imageDict = [NSMutableDictionary dictionary];

[imageDict setValue:imageData

forKey:(id)kQLPreviewPropertyAttachmentDataKey];

if (QLPreviewRequestIsCancelled(preview)) return noErr;

NSMutableDictionary *attachments = [NSMutableDictionary dictionary];

[attachments setValue:imageDict forKey:@"preview-image"];

NSMutableDictionary *properties = [NSMutableDictionary dictionary];

[properties setValue:attachments

forKey:(id)kQLPreviewPropertyAttachmentsKey];

[properties setValue:@"text/html"

forKey:(id)kQLPreviewPropertyMIMETypeKey];

[properties setValue:@"UTF-8"

forKey:(id)kQLPreviewPropertyTextEncodingNameKey];

[properties setValue:@"Recipe"

forKey:(id)kQLPreviewPropertyDisplayNameKey];

For Quick Look to be able to use the HTML page that we’re handing to it, it requires that we describe the document and include any attachments it has. This helps improve the performance of the HTML rendering, since it doesn’t have to fetch any of the attachments. Therefore, in this section, we’re setting up the properties for the HTML page, including specifying its encoding, the MIME type, and the attachments. We also give it a display name that will be used outside the HTML page.

Spotlight/QuickLookPlugin/GeneratePreviewForURL.m

NSBundle *bundle = [NSBundle bundleWithIdentifier:bundleID];

NSString *tPath = [bundle pathForResource:@"preview" ofType:@"html"];

NSURL *templateURL = [NSURL fileURLWithPath:tPath];

NSError *error = nil;

NSXMLDocument *temp;

temp = [[NSXMLDocument alloc] initWithContentsOfURL:(NSURL*)templateURL

options:NSXMLDocumentTidyHTML

error:&error];

if (!template) {

NSLog(@"Failed to build template: %@", error);

return noErr;

}

Once all the preliminaries are complete, we need to retrieve the HTML template from our bundle. Since this code isn’t actually being called from our bundle, we can’t just perform [NSBundle mainBundle] and get a reference to our NSBundle. (If we tried, we’d actually get a reference to /usr/bin/qlmanage!) Instead, we have to request it by its UTI. With a reference to the bundle, we can then retrieve the path to preview.html, which we’ll be using as our template. Once we’ve loaded the HTML file into an NSXMLDocument, it’s time to replace the placeholders in that file with real data.

Spotlight/QuickLookPlugin/GeneratePreviewForURL.m

//Updating the Title

error = nil;

NSXMLElement *element = nil;

element = [[temp nodesForXPath:@"/html/body/div/*[@id='title']"

error:&error] lastObject];

if (!element) {

NSLog(@"Failed to find element: %@", error);

return noErr;

}

[element setStringValue:[metadata valueForKey:(id)kMDItemDisplayName]];

//Updating the description

error = nil;

element = [[template nodesForXPath:@"/html/body/div/*[@id='description']"

error:&error] lastObject];

if (!element) {

NSLog(@"Failed to find element: %@", error);

return noErr;

}

[element setStringValue:[metadata valueForKey:(id)kMDItemTextContent]];

//Updating the serves value

error = nil;

element = [[template nodesForXPath:@"/html/body/div/*[@id='serves']"

error:&error] lastObject];

if (!element) {

NSLog(@"Failed to find element: %@", error);

return noErr;

}

NSNumber *serves = [metadata valueForKey:@"kPPServes"];

[element setStringValue:[NSString stringWithFormat:@"Serves: %li",

(long)[serves integerValue]]];

//Updating the last served value

error = nil;

element = [[template nodesForXPath:@"/html/body/div/*[@id='last_served']"

error:&error] lastObject];

if (!element) {

NSLog(@"Failed to find element: %@", error);

return noErr;

}

NSDate *lastServedDate = [metadata valueForKey:(id)kMDItemLastUsedDate];

if (lastServedDate) {

NSDateFormatter *dateFormatter;

dateFormatter = [[NSDateFormatter alloc] init];

[dateFormatter setDateStyle:NSDateFormatterMediumStyle];

[dateFormatter setTimeStyle:NSDateFormatterNoStyle];

[element setStringValue:[NSString stringWithFormat:@"Last Served: %@",

[dateFormatter stringFromDate:lastServedDate]]];

} else {

[element setStringValue:@"Last Served: Never"];

}

We know the shape of the HTML document, so we can build simple XPath queries to retrieve each part of the document and replace its value component with data from our metadata in NSDictionary.

Spotlight/QuickLookPlugin/GeneratePreviewForURL.m

QLPreviewRequestSetDataRepresentation(preview,

(__bridge CFDataRef)[template XMLData],

kUTTypeHTML,

(__bridge CFDictionaryRef)properties);

}

return noErr;

}

Once all the data has been put into the HTML document, it’s time to render it and set the QLPreviewRequestRef. As this section of code shows, we’re passing in the reference along with the HTML file as data and the property NSDictionary. When this is complete, we pop the NSAutoreleasePool and return noErr. Quick Look now generates our preview and presents it to the user.

Testing the Quick Look Plug-In

As of this writing, testing the Quick Look plug-in is a little more challenging than testing its Spotlight counterpart. Although there is a command-line option to test it, getting the system to recognize the plug-in is a bit trickier. The issue is that the system tends to ignore what generator we want it to use and will use the generator defined for the system.

In writing this chapter, I used this workflow to test the Quick Look plug-in:

1. Clean and build the main recipe application.

2. On the command line, execute qlmanage -r to reset the Quick Look generators.

3. Run the recipe application, which causes our Quick Look generator to get registered.

4. From the command line (can also be done in Xcode), I ran qlmanage -p ${path to metadata test file}, which generated the preview. Using the -t switch instead would produce the thumbnail.

5. Rinse and repeat.

After a few iterations the Quick Look plug-in will be used by the system. Fortunately, this isn’t a situation that’s an issue in production.

Putting It All Together

With a Spotlight importer and a Quick Look generator, it’s possible to do some very interesting things in Mac OS X. For example, we can build a smart folder that finds all our recipes. We can then put that smart folder in the sidebar of Finder and easily access all our recipes directly from the Finder. Further, we can turn on Cover Flow for this smart folder and smoothly browse through the pictures of our recipes, as shown here:

With the included metadata, this opens up quite a few ideas. For example, along with each recipe, we’re storing the time it was last served in the metadata. We can use this information to further refine our smart folder to display only those recipes that we haven’t served in the last 30 days. It’s possible to get creative with metadata now that the operating system is aware of it.

Wrapping Up

With UTIs, it’s possible to integrate even further with the operating system, Spotlight, and Quick Look. It’s possible to publish a full description of the UTI—effectively injecting it into the tree and thus having the data type appear in Spotlight rules and more. However, this is beyond the scope of this book. Here are some exercises you can try on your own.

Decreasing the Size of the Metadata Files

Depending on the application, it’s possible to reduce the metadata files dramatically. Since the importer (and the generator) can stand up the entire Core Data stack, it’s possible to just have the NSManaged-ObjectID (or even a unique identifier within the Recipe object) stored in the metadata file and have the importers and generators retrieve all the metadata information from the Core Data stack instead. (This is probably very similar to how Core Data does it internally.) This would also simplify the updating of the metadata, since the only action required at that point would be to delete metadata files for records that no longer exist. However, care must be taken with this approach because performance may suffer greatly.

Improving the Quick Look Thumbnail Generator

You may have noticed that we ignored the Max Size setting of the Quick Look thumbnail generator. That was done for the sake of clarity, and in a production system we should be sizing down the image to accommodate that setting. By doing so, we’d be good citizens as well as help the performance of Quick Look whenever our files are involved.

Document-Based Applications

When writing an application that uses a document model as opposed to a single repository, integrating Spotlight and Quick Look is even easier. Instead of having separate metadata files, we can simply store the relevant information in the metadata of the actual documents. This allows the importers to read the metadata without having to initialize the entire Core Data stack and still allows for very quick access to the relevant information.

Adding Spotlight and Quick Look integration into your desktop application gives it that bit of polish that subconsciously lets your users know they’re dealing with a finished product.

From here we’ll delve into a Core Data “Recipe” that deals with storing those “bits” of data that don’t quite fit into the normal data mold.

Footnotes

[5]

For more information on UTIs, I suggest reviewing http://developer.apple.com/library/ios/#documentation/general/conceptual/DevPedia-CocoaCore/UniformTypeIdentifier.html.

Copyright © 2016, The Pragmatic Bookshelf.

Chapter 12

Dynamic Parameters

If you have a document-style application, you’ll need to work with document-specific parameters or settings. For example, in a word processor, some settings are specific to one document, and some settings apply to the entire application. We have access to a great implementation for storing application-level parameters: NSUserDefaults. However, there’s no reusable storage system for document-level parameters provided by the APIs. In this chapter, we’ll build that reusable storage system within Core Data.

System-level and user-level preferences are extremely useful and easy to access on OS X. One call to standardDefaults on NSUserDefaults from anywhere in the application instantly gives you access to the defaults for the currently logged-in user. However, sometimes we don’t want to store preferences at the user level but would prefer to store them at the file level.

When we’re working with a Core Data application, the natural first solution may seem to be to create a table for these parameters and access them from within the Core Data API. The problem with this solution occurs when we’re accessing those parameters. No longer is it a single call to standardDefaults on NSUserDefaults; now it looks more like this:

CDPreferences/MyDocument.m

- (void)clunkyParameterAccess

{

NSManagedObjectContext *moc = [self managedObjectContext];

NSFetchRequest *request = [[NSFetchRequest alloc] init];

[request setEntity:[NSEntityDescription entityForName:@"parameter"

inManagedObjectContext:moc]];

[request setPredicate:[NSPredicate predicateWithFormat:@"name == %@",

@"default1"]];

NSError *error = nil;

NSManagedObject *param = [[moc executeFetchRequest:request

error:&error] lastObject];

if (error) {

DLog(@"Error fetching param: %@\n%@", [error localizedDescription],

[error userInfo]);

return;

}

NSLog(@"Parameter value %@", [param valueForKey:@"value"]);

}

Worse is when we need to set a parameter.

CDPreferences/MyDocument.m

- (void)clunkyParameterWrite

{

NSManagedObjectContext *moc = [self managedObjectContext];

NSFetchRequest *request = [[NSFetchRequest alloc] init];

[request setEntity:[NSEntityDescription entityForName:@"parameter"

inManagedObjectContext:moc]];

[request setPredicate:[NSPredicate predicateWithFormat:@"name == %@",

@"default1"]];

NSError *error = nil;

NSManagedObject *param = [[moc executeFetchRequest:request

error:&error] lastObject];

if (error) {

DLog(@"Error fetching param: %@\n%@", [error localizedDescription],

[error userInfo]);

return;

}

if (!param) {

param = [NSEntityDescription insertNewObjectForEntityForName:@"Parameter"

inManagedObjectContext:moc];

[param setValue:@"default1" forKey:@"name"];

}

[param setValue:@"SomeValue" forKey:@"value"];

}

The most obvious answer to this problem is to abstract away most of the code somewhere so we can hit it with only one line of code. It’d be nice if we could access our document-level parameters with code like this:

CDPreferences/MyDocument.m

if ([[[self preferences] valueForKey:@"default1"] boolValue]) {

//Do something clever

}

and be able to set them with something like this:

CDPreferences/MyDocument.m

[[self preferences] setValue:@"New Value" forKey:@"newKey"];

In this example, that’s exactly what we’re going to do. As we discussed briefly in Chapter 10, OS X: Bindings, KVC, and KVO, every object responds to the ‑valueForUndefinedKey: and ‑setValue:forUndefinedKey: methods. We can use (or abuse) these methods and make them do the heavy lifting for us.

Building the Xcode Example Project

To start this project, we’ll use the Core Data document-based application template from within Xcode. In a document-based application, each document object has its own Core Data stack, as opposed to a single Core Data stack for the entire application.

Once we’ve created the project, named CDPreferences, we need to create the data model. For this example, we’re going to focus only on the parameters and build the parameters table shown here. Each parameter has two properties: a name that’s a nonoptional string and a value that’s an optional string. By making the value optional, we can have parameters that are nullable.

With no additional code changes, our application will correctly start up and display an empty document. Since each document has its own persistent store, the persistent store becomes the document that’s being saved to disk. The next step is to build the object that will manage the parameters.

The DocumentPreferences Object

To build a system that imitates the NSUserDefaults, we need to have a single object that manages the parameters table for us. By doing so, we can treat the entire parameters table as if it were a single object with a dynamic number of accessors. However, we don’t want to have to write an accessor every time that we add a parameter; ideally, we want to just call ‑valueForKey: and ‑setValue:forKey: and not worry about the persistence of these values. Lastly, we want to be able to set up some default values.

An important point about the defaults is that they aren’t persisted to disk. If they get persisted, then later versions that change the default would require additional code to check for persisted defaults and reset them. If, however, we don’t persist them, users of newer versions of the application automatically get the newer defaults for free and, more importantly, don’t get their preferences destroyed if they’ve changed the value from its default.

The DocumentPreferences object accomplishes all of these goals.

CDPreferences/DocumentPreferences.h

@interface DocumentPreferences : NSObject

@property (weak) NSPersistentDocument *associatedDocument;

@property (weak) NSDictionary *defaults;

- (id)initWithDocument:(NSPersistentDocument*)associatedDocument;

- (NSArray*)allParameterNames;

- (NSDictionary*)allParameters;

@end

Our DocumentPreferences object expects to receive a reference to its NSPersistentDocument upon initialization. From the passed-in reference, our DocumentPreferences will be able to access the underlying NSManagedObjectContext. We could also just incorporate this design directly into a subclass of NSPersistentDocument; however, that can cause the document object to become quite large and difficult to maintain. Therefore, even though a one-to-one relationship exists between NSPersistentDocument objects and DocumentPreferences objects, we keep them separate to reduce code complexity.

The one thing that’s missing from this header file is any way to access the parameters themselves. There are no methods for this access because we’re going to take advantage of KVC. Whenever another piece of code requests a parameter from our DocumentPreferences object, the ‑valueForUndefinedKey: method is called, and that’s where we handle access to the parameters table.

‑valueForUndefinedKey:

In this method, we receive the name of the value the caller is trying to retrieve.

CDPreferences/DocumentPreferences.m

- (id)valueForUndefinedKey:(NSString*)key

{

id parameter = [self findParameter:key];

if (!parameter && [[self defaults] objectForKey:key]) {

return [[self defaults] objectForKey:key];

}

return [parameter valueForKey:@"value"];

}

Use this name to retrieve the NSManagedObject via the ‑findParameter: method and return the NSManagedObject object’s value property. If there’s no parameter with the passed-in name, check the defaults NSDictionary to see if there’s a default for it. If no default is set, let the ‑valueForKey: method return nil to the caller.

‑findParameter:

In the ‑findParameter: method, construct an NSFetchRequest against the parameters table using a compare on the name property to filter it down to a single result.

CDPreferences/DocumentPreferences.m

- (NSManagedObject*)findParameter:(NSString*)name;

{

NSManagedObjectContext *moc;

NSManagedObject *param;

NSError *error = nil;

moc = [[self associatedDocument] managedObjectContext];

NSFetchRequest *request = [[NSFetchRequest alloc] init];

[request setEntity:[NSEntityDescription entityForName:@"Parameter"

inManagedObjectContext:moc]];

NSPredicate *predicate = nil;

predicate = [NSPredicate predicateWithFormat:@"name == %@", name];

[request setPredicate:predicate];

param = [[moc executeFetchRequest:request error:&error] lastObject];

if (error) {

DLog(@"Error fetching parameter: %@\n%@", [error localizedDescription],

[error userInfo]);

return nil;

}

return param;

}

Assuming there’s no error on the fetch, return the NSManagedObject. In this method, we’re using the ‑lastObject method on the resulting array as a convenience. ‑lastObject automatically checks for an empty array and returns nil if it is empty. This reduces the complexity and gives us the result we want in a single call. If there’s an error accessing the Core Data stack, report the error and return nil. Note that we don’t create a parameter if there isn’t one in this method. We intentionally separate this out so that we aren’t creating potentially empty parameters. This allows us to request a parameter and check if it’s nil without the concern of generating parameters unnecessarily.

‑setValue:forUndefinedKey:

In addition to being able to access a parameter, we need to set parameters. This is done in the counterpart method of ‑valueForUndefinedKey: called ‑setValue:forUndefinedKey:. We first notify the system we’re going to be changing the value associated with the passed-in key. This is part of KVO and is required so that notifications work correctly. After starting the KVO notification, attempt to retrieve the NSManagedObject from the parameters table. If there’s no NSManagedObject for the passed-in key, check the defaults NSDictionary to see whether there’s a default. If there’s a default set and the passed-in value matches the default, complete the KVO notification and return. If the default value doesn’t match the passed-in value, create a new NSManagedObject for the passed-in key.

CDPreferences/DocumentPreferences.m

- (void)setValue:(id)value forUndefinedKey:(NSString*)key

{

[self willChangeValueForKey:key];

NSManagedObject *parameter = [self findParameter:key];

if (!parameter) {

if ([[self defaults] valueForKey:key] &&

[value isEqualTo:[[self defaults] valueForKey:key]]) {

[self didChangeValueForKey:key];

return;

}

parameter = [self createParameter:key];

} else {

if ([[self defaults] valueForKey:key] &&

[value isEqualTo:[[self defaults] valueForKey:key]]) {

[self willChangeValueForKey:key];

NSManagedObjectContext *moc = nil;

moc = [[self associatedDocument] managedObjectContext];

[moc deleteObject:parameter];

[self didChangeValueForKey:key];

return;

}

}

if ([value isKindOfClass:[NSNumber class]]) {

[parameter setValue:[value stringValue] forKey:@"value"];

} else if ([value isKindOfClass:[NSDate class]]) {

[parameter setValue:[value description] forKey:@"value"];

} else {

[parameter setValue:value forKey:@"value"];

}

[self didChangeValueForKey:key];

}

If there’s an NSManagedObject and a default set for the key, compare the default and passed-in values. If they match, we delete the NSManagedObject, which resets the parameter to the default. Once we pass the checks against the default and/or create the NSManagedObject, we test the value to see if it’s an NSNumber or NSDate. If it is, pass in its ‑stringValue or ‑description as the value for the NSManagedObject. Otherwise, pass in the value directly to the NSManagedObject. Once the value is set, call ‑didChangeValueForKey: to complete the KVO notification.

‑createParameter:

The ‑createParameter: method creates a new NSManagedObject and sets the name property with the passed-in value.

CDPreferences/DocumentPreferences.m

- (NSManagedObject*)createParameter:(NSString*)name

{

NSManagedObject *param;

NSManagedObjectContext *moc;

moc = [[self associatedDocument] managedObjectContext];

param = [NSEntityDescription insertNewObjectForEntityForName:@"Parameter"

inManagedObjectContext:moc];

[param setValue:name forKey:@"name"];

return param;

}

It doesn’t set the value property, leaving that up to the caller. This allows us to set a nil parameter if we really need one.

‑allParameters

We have a couple of useful convenience methods. The first, ‑allParameters, returns an NSDictionary of all the parameters, including the defaults. In this method, we create an NSFetchRequest for the Parameter entity without an NSPredicate.

CDPreferences/DocumentPreferences.m

- (NSDictionary*)allParameters;

{

NSManagedObjectContext *moc;

NSError *error = nil;

moc = [[self associatedDocument] managedObjectContext];

NSFetchRequest *request = [[NSFetchRequest alloc] init];

[request setEntity:[NSEntityDescription entityForName:@"Parameter"

inManagedObjectContext:moc]];

NSArray *params = [moc executeFetchRequest:request error:&error];

if (error) {

DLog(@"Error fetching parameter: %@\n%@", [error localizedDescription],

[error userInfo]);

return nil;

}

NSMutableDictionary *dict = [[self defaults] mutableCopy];

for (NSManagedObject *param in params) {

NSString *name = [param valueForKey:@"name"];

NSString *value = [param valueForKey:@"value"];

[dict setValue: value forKey:name];

}

return dict;

}

We take the resulting NSArray from the fetch and loop over it. Within that loop, we add each NSManagedObject to an NSMutableDictionary derived from the default NSDictionary. This ensures we have both the default values and the Parameter entries included in the final NSDictionary.

‑allParameterNames

Like ‑allParameters, ‑allParameterNames is a convenience method that returns an NSArray of the keys currently set or defaulted.

CDPreferences/DocumentPreferences.m

- (NSArray*)allParameterNames;

{

NSManagedObjectContext *moc;

NSError *error = nil;

moc = [[self associatedDocument] managedObjectContext];

NSFetchRequest *request = [[NSFetchRequest alloc] init];

[request setEntity:[NSEntityDescription entityForName:@"Parameter"

inManagedObjectContext:moc]];

NSArray *params = [moc executeFetchRequest:request error:&error];

if (error) {

DLog(@"Error fetching parameter: %@\n%@", [error localizedDescription],

[error userInfo]);

return nil;

}

NSMutableArray *keys = [[[self defaults] allKeys] mutableCopy];

for (NSManagedObject *param in params) {

NSString *name = [param valueForKey:@"name"];

[keys addObject:name];

}

return keys;

}

Just like the ‑allParameters method, it retrieves all the parameter NSManagedObject objects and loops over them. Within that loop, it adds the name property to an NSMutableArray derived from the defaults NSDictionary.

Using Document Preferences

With this design, we can access our parameters within each document without having to worry about the underlying structure. We also don’t need to stop coding just to hop over and add a parameter to the object. We can work with DocumentPreferences in the same manner that we work with NSUserDefaults.

This same design can be used in a nondocument application by changing the DocumentPreferences object by directly adding the methods ‑valueForUndefinedKey: and ‑setValue:forUndefinedKey: to the NSApplicationDelegate along with the NSManagedObjectContext.

Whether or not we’re working in a document model, we can now access persistent store--specific parameters with a single call similar to the following:

NSString *value = [[self preferences] valueForKey:@"exampleKey1"];

We can also set them with a call similar to the following:

[[self preferences] setValue:@"someValue" forKey:@"someKey"];

In both of these examples, we’re calling ‑valueForKey: and ‑setValue:forKey: directly on the DocumentPreferences object and not worrying about whether the value exists. If it doesn’t exist, we’ll receive a nil. If it has been set as a default, we get the default back, and if we’ve overridden the default or previously set the property, it’s returned.

Lastly, like the NSUserDefaults, the default values aren’t persisted to disk. Therefore, we need to set them every time we initialize the DocumentPreferences.

CDPreferences/MyDocument.m

NSMutableDictionary *defaults = [NSMutableDictionary dictionary];

[defaults setValue:[NSNumber numberWithBool:YES] forKey:@"default1"];

[defaults setValue:@"DefaultValue2" forKey:@"default2"];

[defaults setValue:@"DefaultValue3" forKey:@"default3"];

[[self preferences] setDefaults:defaults];

However, we don’t need to worry about changing the defaults at a later date. If we change the defaults in a subsequent version, they’ll automatically be updated if the user hasn’t overridden them.

Wrapping Up

When we’re working with an application that stores data via Core Data, it only makes sense to store preferences in the same system instead of spreading persistence across two or more different APIs. By building an object like the one presented in this chapter, we can store object structures as well as miscellaneous data in Core Data.

Copyright © 2016, The Pragmatic Bookshelf.

Bibliography

[Cla16]

Chris Adamson with Janie Clayton. iOS 9 SDK Development. The Pragmatic Bookshelf, Raleigh, NC, 2016.

Copyright © 2016, The Pragmatic Bookshelf.

You May Be Interested In…

Click a cover for more information

Table of Contents

Introduction

Is This Book for You?

What Is Core Data?

Online Resources

1. Building a Foundation

The Storyboard

The Recipe List

The Recipe Detail

The Edit Workflow

Ready for Core Data

2. Under the Hood of Core Data

Introducing the NSManagedObjectModel

Integrating with the NSPersistentStoreCoordinator

Adding the NSManagedObjectContext

Working with NSManagedObject Instances

Building an NSFetchRequest

NSSortDescriptor

Wrapping Up

3. iOS: NSFetchedResultsController

How to Use the NSFetchedResultsController

Under the Hood of the NSFetchedResultsController

Building Our Own: ZSContextWatcher

Wrapping Up

4. Versioning and Migration

Some Maintenance Before We Migrate

A Simple Migration

The Difference Between Light and Heavy Migrations

A Heavy/Manual Migration

Fundamentals of Core Data Versioning

Progressive Data Migration (An Academic Exercise)

Wrapping Up

5. Performance Tuning

Persistent Store Types

Optimizing Your Data Model

Fetching

Faulting

Access Patterns

Wrapping Up

6. Threading

Threading and Core Data

Working on the Main Queue

Working off the Main Queue

Interqueue Communication

Parent/Child NSManagedObjectContext Design

Export Operation

Import Operation

Asynchronous Saving

Debug Concurrency Checking

Wrapping Up

7. Bulk Changes

Running with Scissors

Doing Bulk Updates

Notifying the Application of Changes

Bulk Deletes

Things to Consider When Using the Bulk Change APIs

Wrapping Up

8. Using Core Data with iCloud

Introducing the UIManagedDocument

Direct NSManagedObjectContext to iCloud

Consuming Changes from iCloud

Under the Hood

Migrating an Existing Application

Desktop iCloud Integration

Issues with Data Quantities

Sharing Data Between iOS and OS X

Wrapping Up

9. Adding a Desktop Foundation

Our Application

Our Application Design

Sharing the Data Model

Building the Controller Layer

Building the User Interface

Adding a Splash of Code

Wrapping Up

10. OS X: Bindings, KVC, and KVO

Key Value Coding

Key Value Observing

Cocoa Bindings and Core Data

Other Interface Elements That Use KVO, KVC, and Core Data

Wrapping Up

11. Spotlight, Quick Look, and Core Data

Integrating with Spotlight

Integrating with Quick Look

Putting It All Together

Wrapping Up

12. Dynamic Parameters

Building the Xcode Example Project

The DocumentPreferences Object

Using Document Preferences

Wrapping Up

Bibliography