Chapter    10

Core Data

Core Data is a technology that is used to solve the problem of data persistence in applications. When users add to the object graph or make changes to the object graph, they generally expect those changes to be reflected in the application the next time that they use it.

For you to provide this, you need to come up with a way for your applications to remember these changes to the object graph. This is what data persistence is all about, and Core Data is the technology that you can use to solve this problem. The recipes in this chapter will show you how to:

  • Add Core Data support to your Mac and iOS applications
  • Compose an entity description
  • Create a managed object
  • Execute fetch requests
  • Execute fetch requests with NSPredicate
  • Execute fetch requests with NSSortDescriptor
  • Post changes to your object graph to the data store
  • Represent to-many relationships with Core Data

NOTE: Core Data can be pretty complex and requires a few steps to set it up. The first three recipes are required before you can build and test your project.

10.1 Adding Core Data Support to an Application

Problem

You want to add Core Data support to your iOS or Mac application.

Solution

Link to the Core Data framework and add the Core Data stack to the class that you would like to support Core Data.

How It Works

Core Data is used to store object data for an application. While Core Data may use a database or file to hold the object content, you don’t need to know these details to use Core Data. You do need to link first to the Core Data framework and set up some Core Data objects in order to use Core Data for your objects.

For this recipe, you’re going to re-create the object graph that was used in the Mac app that you created in Recipe 9.1. This time, however, you will use an iOS app and Core Data to compose the object graph. Core Data is something that you can use with either Mac or iOS.

NOTE: Xcode provides a checkbox titled “Use Core Data for Storage” that will do some of this setup work for you automatically. You can use that as an alternative to this recipe, but be aware that the application template won’t match exactly what you are doing here.

Link to Core Data Framework

Your iOS application is not necessarily linked to Core Data so you need to do this yourself. To link to a framework, go to your Xcode project’s Linked Frameworks pane. See Figure 10-1 to locate this.

Image

Figure 10-1. Linking to the Core Data Framework

Click the plus button in your Linked Frameworks pane (marked (3) in Figure 10-1). You will get a screen with a list of all the available frameworks. There is also a search bar that you can use to filter the list to make it easier to locate the Core Data framework. See Figure 10-2 as a reference.

Image

Figure 10-2. Choosing the Core Data framework

Select the item named CoreData.framework and click the Add button.

Adding the Core Data Stack

Core Data needs some key objects to work. These objects are referred to as the Core Data stack. You need these objects located in the class where you want to implement data persistence. Often you will see the Core Data stack located in the app delegate or a root model class.

In this recipe you are going to add Core Data support to the object graph that you created in Recipe 9.1; you are going to add a new model class that will apply to the entire application. This will serve as your root model and you will locate the Core Data stack here along with an array that will later be used to store a list of projects.

The first step is to create the new root model class that you can simply name AppModel (see Recipe 1.3 for more details on how to add a custom class to your Xcode project). The first thing that you need to add to the AppModel class is a function that returns the URL that you are going to use for your data store.

NOTE: The data store is the file that stores the data on the user’s device in the application’s Documents directory. This can be a SQLite database or another file. While you need to supply Core Data with this URL, you don’t need to worry about the specifics of the database, nor do you need to create the database yourself. If the file or database is not present, Core Data will create it for you.

Here is the function that returns the URL of the data store:

#import "AppModel.h"

@implementation AppModel

- (NSURL *)dataStoreURL {

    NSString *docDir =
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,  Image
NSUserDomainMask, YES) lastObject];

    return [NSURL fileURLWithPath:[docDir Image
stringByAppendingPathComponent:@"DataStore.sql"]];
}

@end

Next, add the managed object model to AppModel. The managed object model maintains a collection of data schemas. Data schemas are specifications of collections of the entities that make up your object graph. These specifications are used by Core Data to figure out how to make object data persistent. In a later recipe, you will compose the documents that Core Data uses for these data schemas.

You are going to add the managed object model to AppModel by adding a property of type NSManagedObjectModel. This property is marked as readonly in the AppModel interface. Since NSManagedObjectModel is a Core Data class, you need to import Core Data into AppModel here as well.

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@interface AppModel : NSObject

-(NSURL *)dataStoreURL;

@property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel;

@end

Moving over to the AppModel implementation file, you must code your own assessor for this readonly property.

#import "AppModel.h"

@implementation AppModel
NSManagedObjectModel *_managedObjectModel;

...

- (NSManagedObjectModel *)managedObjectModel {
    if (_managedObjectModel) {
        return _managedObjectModel;
    }
    _managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
    return _managedObjectModel;
}

@end

This readonly property assessor is lazily creating the managed object model only if the object hasn’t already been created. The managed object model will be created with each schema that you have included in your project.

Next you need the persistent store coordinator. This part of the Core Data stack is responsible for connecting the data store to the managed object model. The persistent store coordinator is also used by the managed object content (you add this next) to persist changes to the object graph.

You add this Core Data stack object to the AppModel class following the same pattern used for the managed object model.

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (_persistentStoreCoordinator) {
        return _persistentStoreCoordinator;
    }

    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] Image
initWithManagedObjectModel:[self managedObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                   configuration:nil
                                                             URL:[self dataStoreURL]
                                                         options:nil
                                                           error:&error]) {
        NSLog(@"Unresolved Core Data error with persistentStoreCoordinator: %@, %@", Image
error, [error userInfo]);
    }

    return _persistentStoreCoordinator;
}

This one is a little more involved because you need to create the store coordinator with a reference to the managed object model and you need to add the data store reference so Core Data knows where to manage the object data.

NOTE: You are not explicitly listing the interface for the persistent store coordinator or the managed object context here since they follow the same pattern as the managed object model. Listing 10-2 shows the entire interface.

Of course, the persistent store coordinator and the next Core Data stack object you add both need a property declaration like the managed object model did. These all follow the same pattern so I won’t repeat that code here, but you can look at Listing 10-2 for the remaining property declarations.

Next, you need the managed object context. This core data stack object is responsible for managing a collection of managed objects. Managed objects are the objects for which Core Data is responsible. These are the objects that need data persistence.

The managed object context acts like a scratch pad for all the changes to the object graph. At key points in an application’s lifecycle, you will use the managed object context to retrieve objects and post the changes to the object graph back to the data store. Here is how to add the managed object context:

- (NSManagedObjectContext *)managedObjectContext {
    if (_managedObjectContext) {
        return _managedObjectContext;
    }

    if ([self persistentStoreCoordinator]) {
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:[self Image
persistentStoreCoordinator]];
    }

    return _managedObjectContext;
}

As you can see from the function, the managed object context simply needs a reference to the persistent store coordinator to function. That’s the Core Data stack for iOS. This gives you Core Data support, but there are other steps you need to take before you can show how Core Data works in a real application.

See Listings 10-1 through 10-4.

The Code

Listing 10-1. AppModel.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@interface AppModel : NSObject

-(NSURL *)dataStoreURL;

@property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, strong, readonly) NSPersistentStoreCoordinator Image
*persistentStoreCoordinator;
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;

@end

Listing 10-2. AppModel.m

#import "AppModel.h"

@implementation AppModel
NSManagedObjectModel *_managedObjectModel;
NSPersistentStoreCoordinator *_persistentStoreCoordinator;
NSManagedObjectContext *_managedObjectContext;

- (NSURL *)dataStoreURL {

    NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, Image
NSUserDomainMask, YES) lastObject];

    return [NSURL fileURLWithPath:[docDir stringByAppendingPathComponent:@"DataStore.sql"]];
}

- (NSManagedObjectModel *)managedObjectModel {
    if (_managedObjectModel) {
        return _managedObjectModel;
    }
    _managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
    return _managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (_persistentStoreCoordinator) {
        return _persistentStoreCoordinator;
    }

    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] Image
initWithManagedObjectModel:[self managedObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                   configuration:nil
                                                             URL:[self dataStoreURL]
                                                         options:nil
                                                           error:&error]) {
        NSLog(@"Unresolved Core Data error with persistentStoreCoordinator: %@, %@", Image
error, [error userInfo]);
    }

    return _persistentStoreCoordinator;
}

- (NSManagedObjectContext *)managedObjectContext {
    if (_managedObjectContext) {
        return _managedObjectContext;
    }

    if ([self persistentStoreCoordinator]) {
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:[self Image
persistentStoreCoordinator]];
    }

    return _managedObjectContext;
}

@end

Listing 10-3. AppDelegate.h

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

Listing 10-4. AppDelegate.m

#import "AppDelegate.h"

@implementation AppDelegate
@synthesize window = _window;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:  Image
(NSDictionary *)launchOptions{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

@end

Usage

Core Data requires you to set up a few things before you can actually test code. For now, you can simply link to the Core Data framework and add the AppModel class to an iOS app. Build your application and make sure that you don’t see any errors. The upcoming recipes will assume that you have the Core Data stack in place.

10.2 Adding an Entity Description

Problem

You need to describe the entity that will be managed by Core Data.

Solution

Add a data model file to your application and then use the data model editor to describe your entity.

How It Works

You can use Xcode to lay out entities and attributes. Store these entity descriptions in a special file called a data model. For this recipe, you will create an entity description for the entity project. This is the same project that you set up in Recipe 9.1.

The first thing you need to do is add the data model to your application. From Xcode, go to File Image New Image File. Then choose iOS Image Core Data Image Data Model. You can name the file whatever you want, but I’ll leave the default name of Model. You should see a new file named Model.xcdatamodeld appear in the Project Navigator. If you select this file, you will see something like Figure 10-3 appear in the editor screen.

Image

Figure 10-3. Data model editor

The data model editor is where you will describe the Project entity. Click the Add Entity button in the bottom left area in the screen. A new entry will appear at the top left area in the data model editor under the title entity. Name your entity Project.

Once you have an entity started, you can describe the entity by adding attributes to it. These attributes will be turned into code properties later. Based on Recipe 9-1, you already know that Project has these attributes: name, description, and a due date.

NOTE: The Project class from Recipe 9.1 also had Worker and listOfTasks properties. These are a little bit more involved, so you’ll revisit these two properties in the upcoming recipe on establishing relationships in Core Data.

To add an attribute to the Project entity, make sure that the Project entity is selected in the data model editor and click the Add Attribute button in the bottom right area of the data model editor. The attribute will appear in the center top area of the data model editor and you can type in the name of the attribute (name, in this case).

To the right of the attribute name you can also choose a data type. Click the drop-box toward the right and select the data type String for the name attribute. Repeat this process for the description attribute, but change the name to descrip.

NOTE: The word “description” is already used by a Core Data class so you can’t use it for the Project entity because there will be a conflict.

Name the due date attribute dueDate and set Date as the data type.

When you are finished your data model should look like Figure 10-4.

Image

Figure 10-4. Completed project data model

That’s all there is to using the data model editor to describe an entity.

The Code

NOTE: The code that appears in Listings 10-5 and 10-6 is the default code that Xcode automatically generates when you create a new iOS application. I’ve made no modifications to it.

Listing 10-5. AppDelegate.h

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

Listing 10-6. AppDelegate.m

#import "AppDelegate.h"

@implementation AppDelegate
@synthesize window = _window;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:  Image
(NSDictionary *)launchOptions{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

@end

Usage

Now you’re ready to move on to Recipe 10.3 and continue setting up Core Data.

10.3 Adding a Managed Object to an Application

Problem

The entity that you composed in Recipe 10.2 needs an Objective-C class so that you can use it in code.

Solution

Use Xcode to automatically generate a code file based on the entity description that you set up in Recipe 10.2.

How It Works

Core Data uses entity descriptions to set up a database schema and to code a class that you can use in your application. All you need to do is add a new Core Data file to your Xcode project. For this recipe, I’m going to assume that you have already set up the Core Data stack from Recipe 10.1 and the project entity description from Recipe 10.2.

Select the data model file that you created in Recipe 10.2. Also, make sure to select the Project entity. Then choose File Image New Image File. Then choose iOS Image Core Data Image NSManaged Object subclass. In the dialog that pops up, click Create.

You will see that two files have been generated for you: Project.h and Project.m. If you click on Project.h, you will see this interface:

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@interface Project : NSManagedObject

@property (nonatomic, retain) NSString * descrip;
@property (nonatomic, retain) NSDate * dueDate;
@property (nonatomic, retain) NSString * name;

@end

This looks like a typical Objective-C class except that Project is a subclass of NSManagedObject and you’re importing the Core Data framework. Being a subclass of NSManagedObject is required for Core Data to be able to take responsibility for Project.

Here is what the implementation for Project looks like:

#import "Project.h"

@implementation Project

@dynamic descrip;
@dynamic dueDate;
@dynamic name;

@end

What’s notable about this is that these property declarations all come from the entity description that you coded in Recipe 10.2. Also, note the @dynamic keyword here. @dynamic is used like @synthesize to deal with the property assessor code.

NOTE: @dynamic means that the class will deal with the property assessor code at runtime. Normally, if you were to use the @dynamic code on your own, you would need some way to have your class respond to requests for property value getting and setting. NSManagedObject does this for you in the background using key-value coding.

That’s all you need to do to create the Project managed object. See Listings 10-7 through 10-12.

The Code

Listing 10-7. AppDelegate.h

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

Listing 10-8. AppDelegate.m

#import "AppDelegate.h"

@implementation AppDelegate
@synthesize window = _window;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:  Image
(NSDictionary *)launchOptions{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

@end

Listing 10-9. AppModel.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@interface AppModel : NSObject

-(NSURL *)dataStoreURL;

@property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, strong, readonly) NSPersistentStoreCoordinator Image
*persistentStoreCoordinator;
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;

@end

Listing 10-10. AppModel.m

#import "AppModel.h"

@implementation AppModel
NSManagedObjectModel *_managedObjectModel;
NSPersistentStoreCoordinator *_persistentStoreCoordinator;
NSManagedObjectContext *_managedObjectContext;

- (NSURL *)dataStoreURL {

    NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, Image
NSUserDomainMask, YES) lastObject];

    return [NSURL fileURLWithPath:[docDir Image
stringByAppendingPathComponent:@"DataStore.sql"]];
}

- (NSManagedObjectModel *)managedObjectModel {
    if (_managedObjectModel) {
        return _managedObjectModel;
    }
    _managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
    return _managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (_persistentStoreCoordinator) {
        return _persistentStoreCoordinator;
    }

    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] Image
initWithManagedObjectModel:[self managedObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                   configuration:nil
                                                             URL:[self dataStoreURL]
                                                         options:nil
                                                           error:&error]) {
        NSLog(@"Unresolved Core Data error with persistentStoreCoordinator: %@, %@", Image
error, [error userInfo]);
    }

    return _persistentStoreCoordinator;
}

- (NSManagedObjectContext *)managedObjectContext {
    if (_managedObjectContext) {
        return _managedObjectContext;
    }

    if ([self persistentStoreCoordinator]) {
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:[self Image
persistentStoreCoordinator]];
    }

    return _managedObjectContext;
}

@end

Listing 10-11. Project.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@interface Project : NSManagedObject

@property (nonatomic, retain) NSString * descrip;
@property (nonatomic, retain) NSDate * dueDate;
@property (nonatomic, retain) NSString * name;

@end

Listing 10-12. Project.m

#import "Project.h"

@implementation Project

@dynamic descrip;
@dynamic dueDate;
@dynamic name;

@end

Usage

You are getting closer to testing Core Data out, but as of yet there is still nothing to test with this code. In the next recipe, you’ll start to see how this all starts to come together.

10.4 Adding a Managed Object to Core Data

Problem

You want to use objects that are managed by Core Data.

Solution

Use the managed object context to create a new managed object and save the managed object to the data store.

How It Works

For this recipe, you will work on the Xcode project from Recipe 10.3 that has the Core Data stack and Project managed object class already set up. To create a new managed object, use the managed object context with the NSEntityDescription class function insertNewObjectForEntityForName:inManagedObjectContext:. This function needs the name of the managed object class Project and it returns a reference to the managed object that was just created.

Project *managedProject = (Project *)[NSEntityDescription Image
insertNewObjectForEntityForName:@"Project"Image
inManagedObjectContext:[self managedObjectContext]];

This gives you a Project object named managedProject. The first thing you should do is set some of this project’s properties.

managedProject.name = @"New Project";
managedProject.descrip = @"This is a new project";
managedProject.dueDate = [NSDate date];

This managedProject object only exists in the managed object context at first. The managed object context functions like a scratch pad where you can place objects before they are ready to be stored in the data store.

Posting Back to the Data Store

Now that you have created content, you need to use the managed object context to post this change back to the data store. To do this, send the save message to the managed object context.

[[self managedObjectContext] save:nil];

Note that this line of code is how you send the save message from the AppModel class. If you want to send the save message from another class, like the app delegate, you can send the save message to the local AppModel reference; you can see this in Listing 10-14.

After you do this, you’ve posted all the changes to the managed object context that were made since the last save message was sent. You can use an NSError object here as a parameter if you wish; for this example I simply used nil. You can retrieve this object from the data store at a later date.

Note that you include this code in the init function, so take a look at Listings 10-13 through 10-18 to see how this all fits into AppModel in context. The code to create a new project managed object is put into a function that returns a Project object so it’s easier to test from other classes like the app delegate.

The Code

Listing 10-13. AppDelegate.h

#import <UIKit/UIKit.h>
#import "AppModel.h"

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

Listing 10-14. AppDelegate.m

#import "AppDelegate.h"

@implementation AppDelegate
@synthesize window = _window;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:  Image
(NSDictionary *)launchOptions{

    //Create a new AppModel instance
    AppModel *dataModel = [[AppModel alloc] init];

    //Get a new project from dateModel and use it
    Project *newProject = [dataModel makeNewProject];
    newProject.name = @"App Delegate's Project";
    NSLog(@"project.name = %@", newProject.name);
    NSLog(@"project.descrip = %@", newProject.descrip);
    NSLog(@"project.dueDate = %@ ", newProject.dueDate);

    //Post changes back to date store
    [[dataModel managedObjectContext] save:nil];

    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

@end

Listing 10-15. AppModel.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "Project.h"

@interface AppModel : NSObject

-(NSURL *)dataStoreURL;
-(Project *)makeNewProject;

@property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, strong, readonly) NSPersistentStoreCoordinator Image
*persistentStoreCoordinator;
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;

@end

Listing 10-16. AppModel.m

#import "AppModel.h"

@implementation AppModel
NSManagedObjectModel *_managedObjectModel;
NSPersistentStoreCoordinator *_persistentStoreCoordinator;
NSManagedObjectContext *_managedObjectContext;

-(Project *)makeNewProject{

    Project *managedProject = (Project *)[NSEntityDescription Image
insertNewObjectForEntityForName:@"Project"Image
inManagedObjectContext:[self managedObjectContext]];

    managedProject.name = @"New Project";
    managedProject.descrip = @"This is a new project";
    managedProject.dueDate = [NSDate date];

    return managedProject;

}

- (NSURL *)dataStoreURL {

    NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, Image
NSUserDomainMask, YES) lastObject];

    return [NSURL fileURLWithPath:[docDir Image
stringByAppendingPathComponent:@"DataStore.sql"]];
}

- (NSManagedObjectModel *)managedObjectModel {
    if (_managedObjectModel) {
        return _managedObjectModel;
    }
    _managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
    return _managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (_persistentStoreCoordinator) {
        return _persistentStoreCoordinator;
    }

    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] Image
initWithManagedObjectModel:[self managedObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                   configuration:nil
                                                             URL:[self dataStoreURL]
                                                         options:nil
                                                           error:&error]) {
        NSLog(@"Unresolved Core Data error with persistentStoreCoordinator: %@, %@", Image
error, [error userInfo]);
    }

    return _persistentStoreCoordinator;
}

- (NSManagedObjectContext *)managedObjectContext {
    if (_managedObjectContext) {
        return _managedObjectContext;
    }

    if ([self persistentStoreCoordinator]) {
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:[self Image
persistentStoreCoordinator]];
    }

    return _managedObjectContext;
}

@end

Listing 10-17. Project.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@interface Project : NSManagedObject

@property (nonatomic, retain) NSString * descrip;
@property (nonatomic, retain) NSDate * dueDate;
@property (nonatomic, retain) NSString * name;

@end

Listing 10-18. Project.m

#import "Project.h"

@implementation Project

@dynamic descrip;
@dynamic dueDate;
@dynamic name;

@end

Usage

At long last, you can now test out this Core Data code for yourself. Add the code from Listings 10-13 through 10-18 and then build and run your Xcode project. When you run your application you will see the following in your console log window:

project.name = App Delegate's Project

project.descrip = This is a new project

project.dueDate = 2012-04-10 14:07:00 +0000

Take a look at AppDelegate.m to see how this output was created. A Project managed object instance was created from the AppModel class and returned to the app delegate. As you can see, this process isn’t much different than using other Objective-C classes and objects.

10.5 Retrieving Objects from the Data Store

Problem

You want users to retrieve objects from the data store that they have worked on earlier.

Solution

Use a fetch request to retrieve objects that are already in the data store.

How It Works

A fetch request is the action of getting objects out of a data store. Use the NSFetchRequest class to create the request and NSEntityDescription to specify the type of entity that you want to retrieve from the data store.

You can create an NSFetchRequest object using the alloc and init constructors.

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

You also need an entity description so Core Data knows what it’s supposed to fetch.

NSEntityDescription *entity = [NSEntityDescription entityForName:@"Project"
                                          inManagedObjectContext:[self Image
managedObjectContext]];

NSEntityDescription *entity = [NSEntityDescription entityForName:@"Project"
                                    inManagedObjectContext:[self managedObjectContext]];

You then must assign the entity object to the fetch request’s entity property.

request.entity = entity;

Finally, you can execute the fetch request. The results will be returned back to you as an array.

NSArray *listOfProjects = [[self managedObjectContext] executeFetchRequest:request Image
error:nil];

You can use this array to reference the managed objects that you have available from the data store. If there are no objects yet, the array will still be created but it will have a count of 0.

Here is an example of how you might use this array of project objects:

//List out contents of each project
if([listOfProjects count] == 0)
    NSLog(@"There are no projects in the data store yet");
else {
    NSLog(@"HERE ARE THE PROJECTS IN THE DATA STORE");
    [listOfProjects enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSLog(@"-----");
        NSLog(@"project.name = %@", [obj name]);
        NSLog(@"project.descrip = %@", [obj descrip]);
        NSLog(@"project.dueDate = %@ ", [obj dueDate]);
    }];
}

See Listings 10-19 through 10-24.

The Code

Listing 10-19. AppDelegate.h

#import <UIKit/UIKit.h>
#import "AppModel.h"

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

Listing 10-20. AppDelegate.m

#import "AppDelegate.h"

@implementation AppDelegate
@synthesize window = _window;

- (BOOL)application:(UIApplication *)application Image
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

    //Create a new AppModel instance
    AppModel *dataModel = [[AppModel alloc] init];

    //Get a new project from dateModel and use it
    Project *newProject = [dataModel makeNewProject];
    newProject.name = @"App Delegate's Project";
    NSLog(@"project.name = %@", newProject.name);
    NSLog(@"project.descrip = %@", newProject.descrip);
    NSLog(@"project.dueDate = %@ ", newProject.dueDate);

    //Post changes back to date store
    [[dataModel managedObjectContext] save:nil];

    //Get all the projects in the data store
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Project"
                                              inManagedObjectContext:[dataModel Image
managedObjectContext]];
    request.entity = entity;
    NSArray *listOfProjects = [[dataModel managedObjectContext] Image
executeFetchRequest:request error:nil];

    //List out contents of each project
    if([listOfProjects count] == 0)
        NSLog(@"There are no projects in the data store yet");
    else {
        NSLog(@"HERE ARE THE PROJECTS IN THE DATA STORE");
      [listOfProjects enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            NSLog(@"-----");
            NSLog(@"project.name = %@", [obj name]);
            NSLog(@"project.descrip = %@", [obj descrip]);
            NSLog(@"project.dueDate = %@ ", [obj dueDate]);
        }];
    }


    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

@end

Listing 10-21. AppModel.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
 #import "Project.h"

@interface AppModel : NSObject

-(NSURL *)dataStoreURL;
-(Project *)makeNewProject;

@property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, strong, readonly) NSPersistentStoreCoordinator Image
*persistentStoreCoordinator;
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;

@end

Listing 10-22. AppModel.m

#import "AppModel.h"

@implementation AppModel
NSManagedObjectModel *_managedObjectModel;
NSPersistentStoreCoordinator *_persistentStoreCoordinator;
NSManagedObjectContext *_managedObjectContext;

-(Project *)makeNewProject{

    Project *managedProject = (Project *)[NSEntityDescription Image
insertNewObjectForEntityForName:@"Project"Image
inManagedObjectContext:[self managedObjectContext]];

    managedProject.name = @"New Project";
    managedProject.descrip = @"This is a new project";
    managedProject.dueDate = [NSDate date];

    return managedProject;

}

- (NSURL *)dataStoreURL {

    NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, Image
NSUserDomainMask, YES) lastObject];

    return [NSURL fileURLWithPath:[docDir Image
stringByAppendingPathComponent:@"DataStore.sql"]];
}

- (NSManagedObjectModel *)managedObjectModel {
    if (_managedObjectModel) {
        return _managedObjectModel;
    }
    _managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
    return _managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (_persistentStoreCoordinator) {
        return _persistentStoreCoordinator;
    }

    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] Image
initWithManagedObjectModel:[self managedObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                   configuration:nil
                                                             URL:[self dataStoreURL]
                                                         options:nil
                                                           error:&error]) {
        NSLog(@"Unresolved Core Data error with persistentStoreCoordinator: %@, %@", Image
error, [error userInfo]);
    }

    return _persistentStoreCoordinator;
}

- (NSManagedObjectContext *)managedObjectContext {
    if (_managedObjectContext) {
        return _managedObjectContext;
    }

    if ([self persistentStoreCoordinator]) {
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:[self Image
persistentStoreCoordinator]];
    }

    return _managedObjectContext;
}

@end

Listing 10-23. Project.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@interface Project : NSManagedObject

@property (nonatomic, retain) NSString * descrip;
@property (nonatomic, retain) NSDate * dueDate;
@property (nonatomic, retain) NSString * name;

@end

Listing 10-24. Project.m

#import "Project.h"

@implementation Project

@dynamic descrip;
@dynamic dueDate;
@dynamic name;

@end

Usage

Test this code by adding Listings 10-19 through 10-24 to your own Xcode project. If you build and run this a few times, you will notice that the list from the data store grows by one project for each run. This happens because the new project that was created in the beginning in the app delegate is being added to all the projects that were already present in the data store. If you were to run this app for the first time, your output would look something like this:

project.name = App Delegate's Project
project.descrip = This is a new project
project.dueDate = 2012-04-12 15:08:13 +0000
HERE ARE THE PROJECTS IN THE DATA STORE
-----
project.name = App Delegate's Project
project.descrip = This is a new project
project.dueDate = 2012-04-12 15:08:13 +0000

NOTE: The managed object context retrieves all the objects in the data store as well as the objects that are in the managed object context but not yet posted to the data store.

10.6 Posting Changes to the Data Store

Problem

As your users work with your application they makes changes to the content that you want to save to the data store.

Solution

Test the managed object context to see if any changes have been made to the user’s object graph. If there have been changes, you can either roll back and get rid of the changes or save the changes back to the data store.

How It Works

Managed objects are used like other Objective-C objects. You can use dot notation to change the content in a managed object. Once you do this, the managed object context becomes aware that changes have been made to the object graph.

For example, if you change the content in the project from Recipe 10.5, you would just do something like this:

newProject.name = @"Project Has New Name";
newProject.descrip = @"Here is a new revision of the project";

What is different from what you’ve done in previous chapters is that the managed object context has become aware of the changes you’ve made. You can ask the managed object context if any changes have been made to the object graph by sending the hasChanges message to the context, like so:

if([[dataModel managedObjectContext] hasChanges])
    NSLog(@"The object graph has changed");

Sometimes you might want to ask the managed object context if anything has changed at key points in an application’s lifecycle. If there are changes, you can either save them to the data store or discard them. Here is how you might save changes:

if([[dataModel managedObjectContext] hasChanges])
    [[dataModel managedObjectContext] save:nil];

Of course, you already saw this operation when you created the first project. You could have also discarded the changes by sending a rollback message to the managed object context.

if([[dataModel managedObjectContext] hasChanges])
    [[dataModel managedObjectContext] rollback];

When you want to delete a managed object you must use the managed object context deleteObject method.

[[dataModel managedObjectContext] deleteObject:newProject];

You still can roll back or save this change permanently like you did when you changed the property values by sending either the rollback or save message to the managed object context. See Listings 10-25 through 10-30.

The Code

Listing 10-25. AppDelegate.h

#import <UIKit/UIKit.h>
#import "AppModel.h"

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

Listing 10-26. AppDelegate.m

#import "AppDelegate.h"

@implementation AppDelegate
@synthesize window = _window;

- (BOOL)application:(UIApplication *)application Image
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

    //Create a new AppModel instance
    AppModel *dataModel = [[AppModel alloc] init];

    //Make some projects
    Project *p1 = [dataModel makeNewProject];
    p1.name = @"Proj1";

    Project *p2 = [dataModel makeNewProject];
    p2.name = @"Proj2";

    Project *p3 = [dataModel makeNewProject];
    p3.name = @"Proj3";

    Project *p4 = [dataModel makeNewProject];
    p4.name = @"Proj4";

    [[dataModel managedObjectContext] save:nil];

    //Get all the projects in the data store
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Project"
                                              inManagedObjectContext:[dataModel Image
managedObjectContext]];
    request.entity = entity;
    NSArray *listOfProjects = [[dataModel managedObjectContext] Image
executeFetchRequest:request error:nil];

    //Print out contents of all the projects
    NSLog(@"-----");
    NSLog(@"NEW PROJECTS IN CONTEXT");
    [listOfProjects enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSLog(@"project.name = %@", [obj name]);
    }];

    //Rollback example
    Project *rollbackProject = [listOfProjects objectAtIndex:0];
    rollbackProject.name = @"Rollback Project";

    //Look at changed object
    NSLog(@"-----");
    NSLog(@"CHANGED PROJECTS IN CONTEXT");
    [listOfProjects enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSLog(@"project.name = %@", [obj name]);
    }];

    //Discard changes
    if([[dataModel managedObjectContext] hasChanges])
        [[dataModel managedObjectContext] rollback];

    //Look at object after rollback
    NSLog(@"-----");
    NSLog(@"PROJECTS IN CONTEXT AFTER ROLLBACK");
    [listOfProjects enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSLog(@"project.name = %@", [obj name]);
    }];

    //Delete second and third projects
    [[dataModel managedObjectContext] deleteObject:p2];
    [[dataModel managedObjectContext] deleteObject:p3];

    //save back to data store
    [[dataModel managedObjectContext] save:nil];

    //Get all the projects in the data store
    request = [[NSFetchRequest alloc] init];
    entity = [NSEntityDescription entityForName:@"Project"
                         inManagedObjectContext:[dataModel managedObjectContext]];
    request.entity = entity;
    listOfProjects = [[dataModel managedObjectContext] executeFetchRequest:request Image
error:nil];

    //Look at objects after deletion
    NSLog(@"-----");
    NSLog(@"PROJECTS IN CONTEXT AFTER DELETION");
    [listOfProjects enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSLog(@"project.name = %@", [obj name]);
    }];

    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

@end

Listing 10-27. AppModel.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
 #import "Project.h"

@interface AppModel : NSObject

-(NSURL *)dataStoreURL;
-(Project *)makeNewProject;

@property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, strong, readonly) NSPersistentStoreCoordinator Image
*persistentStoreCoordinator;
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;

@end

Listing 10-28. AppModel.m

#import "AppModel.h"

@implementation AppModel
NSManagedObjectModel *_managedObjectModel;
NSPersistentStoreCoordinator *_persistentStoreCoordinator;
NSManagedObjectContext *_managedObjectContext;

-(Project *)makeNewProject{

    Project *managedProject = (Project *)[NSEntityDescription Image
insertNewObjectForEntityForName:@"Project"Image
inManagedObjectContext:[self managedObjectContext]];

    managedProject.name = @"New Project";
    managedProject.descrip = @"This is a new project";
    managedProject.dueDate = [NSDate date];

    return managedProject;

}

- (NSURL *)dataStoreURL {

    NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, Image
NSUserDomainMask, YES) lastObject];

    return [NSURL fileURLWithPath:[docDir stringByAppendingPathComponent:@"DataStore.sql"]];
}

- (NSManagedObjectModel *)managedObjectModel {
    if (_managedObjectModel) {
        return _managedObjectModel;
    }
    _managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
    return _managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (_persistentStoreCoordinator) {
        return _persistentStoreCoordinator;
    }

    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] Image
initWithManagedObjectModel:[self managedObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                   configuration:nil
                                                             URL:[self dataStoreURL]
                                                         options:nil
                                                           error:&error]) {
        NSLog(@"Unresolved Core Data error with persistentStoreCoordinator: %@, %@", Image
error, [error userInfo]);
    }

    return _persistentStoreCoordinator;
}

- (NSManagedObjectContext *)managedObjectContext {
    if (_managedObjectContext) {
        return _managedObjectContext;
    }

    if ([self persistentStoreCoordinator]) {
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:[self Image
persistentStoreCoordinator]];
    }

    return _managedObjectContext;
}

@end

Listing 10-29. Project.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@interface Project : NSManagedObject

@property (nonatomic, retain) NSString * descrip;
@property (nonatomic, retain) NSDate * dueDate;
@property (nonatomic, retain) NSString * name;

@end

Listing 10-30. Project.m

#import "Project.h"

@implementation Project

@dynamic descrip;
@dynamic dueDate;
@dynamic name;

@end

Usage

Add the code from Listings 10-25 through 10-30 to your Xcode project to test it for yourself. The recipe is a little bit more involved than the main recipe text. The main difference is that it contains four separate projects so that you can clearly see the effects of saving, rolling back, and deleting managed objects from the data store.

The first time that you run your application you should observe something like this in the console log window:

-----
NEW PROJECTS IN CONTEXT
project.name = Proj3
project.name = Proj4
project.name = Proj1
project.name = Proj2
-----
CHANGED PROJECTS IN CONTEXT
project.name = Rollback Project
project.name = Proj4
project.name = Proj1
project.name = Proj2
-----
PROJECTS IN CONTEXT AFTER ROLLBACK
project.name = Proj3
project.name = Proj4
project.name = Proj1
project.name = Proj2
-----
PROJECTS IN CONTEXT AFTER DELETION
project.name = Proj4
project.name = Proj1

NOTE: The order in which the projects appear may be different for you.

10.7 Using One-To-One Relationships with Core Data

Problem

Your object graph requires you to represent a one-to-one relationship and you want this content managed by Core Data.

Solution

Create at least two entities in the data model and then add a relationship between these entities in the data model editor.

How It Works

You are getting closer to implementing the object graph from Recipe 9.1 in Core Data. What you want to do is add a Worker entity to your data model. Remember that the Worker class from Recipe 9.1 had a name property and a role property. Worker objects could be assigned to projects and tasks. You are going to just recreate the Project to Worker relationship here.

NOTE: You are about to make a big change to your data model. Since the data model is cached after the first time it runs, you can’t change the data model without breaking the application. So you need to make sure to delete the application from the iOS Simulator before testing the changes that you are about to make to the data model. Go to the iOS Simulator and click iOS Simulator Image Reset Content and Settings. Click the Reset button that pops up.

First, add a new Worker entity to your data model. Follow the same procedure as Recipe 10.2 to add the Worker entity. Your updated model should look like Figure 10-5 when you are finished.

Image

Figure 10-5. Worker entity

Next, you need to establish a relationship between Project and Worker. To establish the relationship, select Project in the data model editor and then click the plus button in the Relationships pane of the data model editor. Name the relationship personInCharge and set the Destination to Worker.

Now you need to define the inverse (or opposite) relationship. This gives you a way to reference the project that a worker is working on.

Select the Worker entity and then click the plus button in the Relationships pane of the data model editor. Name the relationship Project and set the Destination to Project. Select personInCharge for the Inverse.

To see everything that you just did at one time, select each entity in the data model editor while holding down the Command key. Both entities will be highlighted and you will see all the attributes and relationships listed at once. Your data model editor should look like Figure 10-6.

Image

Figure 10-6. Project-to-Worker and Worker-to-Project relationship

Keeping both the Project and Worker entities highlighted, go to File Image New Image File. Then choose iOS Image Core Data Image NSManagedObject subclass. Click Next and then Create. You will get a warning dialog because you are going to write over the previous Project class file. That is ok since you do need to update it, so click Replace.

In your Xcode project you should have files for the Project and Worker managed object classes. Let’s take a look at the Project class interface.

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class Worker;

@interface Project : NSManagedObject

@property (nonatomic, retain) NSString * descrip;
@property (nonatomic, retain) NSDate * dueDate;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) Worker *personInCharge;

@end

The relationship for the person in charge is represented by that last property, personInCharge. You can use this property to get a reference to the Worker object with which you have the one-to-one relationship.

Now look at the Worker class interface to see how the opposite relationship is represented.

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class Project;

@interface Worker : NSManagedObject

@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSString * role;
@property (nonatomic, retain) Project *project;

@end

This gives you the opportunity to get a reference to the project when you only have a reference to the worker on hand.

All of this gives you the infrastructure to set up your relationships and entities. But you now need to add the code to create the objects and establish the relationships. In the Core Data recipes you’ve done so far you’ve been using the makeNewProject function in AppModel to do this for you. Logically enough, you need to use a makeNewWorker function to create a Worker instance for you in AppModel.

Change the interface for AppModel to accommodate the function that you need to create a new Worker instance.

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "Project.h"
#import "Worker.h"

@interface AppModel : NSObject

-(NSURL *)dataStoreURL;

@property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, strong, readonly) NSPersistentStoreCoordinator Image
*persistentStoreCoordinator;
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;

-(Project *)makeNewProject;
-(Worker *)makeNewWorker;

@end

The makeNewWorker function can be coded like this:

#import "AppModel.h"

@implementation AppModel

...

-(Worker *)makeNewWorker{
    Worker *managedWorker = (Worker *)[NSEntityDescription Image
insertNewObjectForEntityForName:@"Worker" Image
inManagedObjectContext:[self managedObjectContext]];

    managedWorker.name = @"New Worker";
    managedWorker.Role = @"Works on projects";

    return managedWorker;
}

...

@end

You establish the relationship itself in the makeNewProject function.

#import "AppModel.h"

@implementation AppModel

...

-(Project *)makeNewProject{

    Project *managedProject = (Project *)[NSEntityDescription Image
insertNewObjectForEntityForName:@"Project" Image
inManagedObjectContext:[self managedObjectContext]];

    managedProject.name = @"New Project";
    managedProject.descrip = @"This is a new project";
    managedProject.dueDate = [NSDate date];

    managedProject.personInCharge = [self makeNewWorker];

    return managedProject;

}

...

@end

Now, if you use AppModel to create a new project, you automatically have a Worker assigned and the relationship is established. For instance, you could do something like this:

//Create a new AppModel instance
AppModel *dataModel = [[AppModel alloc] init];

//Make some projects
Project *p1 = [dataModel makeNewProject];
p1.name = @"Proj1";

NSLog(@"p1.name = %@, p1.personInCharge = %@", p1.name, p1.personInCharge.name);

This will print out the following content to the console log:

p1.name = Proj1, p1.personInCharge = New Worker
w.project.name = Proj1

See Listings 10-31 through 10-38.

The Code

Listing 10-31. AppModel.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "Project.h"
#import "Worker.h"

@interface AppModel : NSObject

-(NSURL *)dataStoreURL;

@property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, strong, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;

-(Project *)makeNewProject;
-(Worker *)makeNewWorker;

@end

Listing 10-32. AppModel.m

#import "AppModel.h"

@implementation AppModel
NSManagedObjectModel *_managedObjectModel;
NSPersistentStoreCoordinator *_persistentStoreCoordinator;
NSManagedObjectContext *_managedObjectContext;

-(Project *)makeNewProject{

    Project *managedProject = (Project *)[NSEntityDescription Image
insertNewObjectForEntityForName:@"Project" Image
inManagedObjectContext:[self managedObjectContext]];

    managedProject.name = @"New Project";
    managedProject.descrip = @"This is a new project";
    managedProject.dueDate = [NSDate date];

    managedProject.personInCharge = [self makeNewWorker];

    return managedProject;

}

-(Worker *)makeNewWorker{
    Worker *managedWorker = (Worker *)[NSEntityDescription Image
insertNewObjectForEntityForName:@"Worker" Image
inManagedObjectContext:[self managedObjectContext]];

    managedWorker.name = @"New Worker";
    managedWorker.Role = @"Works on projects";

    return managedWorker;
}

- (NSURL *)dataStoreURL {

    NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, Image
NSUserDomainMask, YES) lastObject];

    return [NSURL fileURLWithPath:[docDir Image
stringByAppendingPathComponent:@"DataStore.sql"]];
}

- (NSManagedObjectModel *)managedObjectModel {
    if (_managedObjectModel) {
        return _managedObjectModel;
    }
    _managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
    return _managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (_persistentStoreCoordinator) {
        return _persistentStoreCoordinator;
    }

    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] Image
initWithManagedObjectModel:[self managedObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                   configuration:nil
                                                             URL:[self dataStoreURL]
                                                         options:nil
                                                           error:&error]) {
        NSLog(@"Unresolved Core Data error with persistentStoreCoordinator: %@, %@", Image
error, [error userInfo]);
    }

    return _persistentStoreCoordinator;
}

- (NSManagedObjectContext *)managedObjectContext {
    if (_managedObjectContext) {
        return _managedObjectContext;
    }

    if ([self persistentStoreCoordinator]) {
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:[self Image
persistentStoreCoordinator]];
    }

    return _managedObjectContext;
}

@end

Listing 10-33. Worker.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class Project;

@interface Worker : NSManagedObject

@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSString * role;
@property (nonatomic, retain) Project *project;

@end

Listing 10-34. Worker.m

#import "Worker.h"
#import "Project.h"

@implementation Worker

@dynamic name;
@dynamic role;
@dynamic project;

@end

Listing 10-35. Project.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class Worker;

@interface Project : NSManagedObject

@property (nonatomic, retain) NSString * descrip;
@property (nonatomic, retain) NSDate * dueDate;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) Worker *personInCharge;

@end

Listing 10-36. Project.m

#import "Project.h"
#import "Worker.h"

@implementation Project

@dynamic descrip;
@dynamic dueDate;
@dynamic name;
@dynamic personInCharge;

@end

Listing 10-37. AppDelegate.h

#import <UIKit/UIKit.h>
#import "AppModel.h"

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

Listing 10-38. AppDelegate.m

#import "AppDelegate.h"

@implementation AppDelegate
@synthesize window = _window;

- (BOOL)application:(UIApplication *)application Image
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

    //Create a new AppModel instance
    AppModel *dataModel = [[AppModel alloc] init];

    //Make some projects
    Project *p1 = [dataModel makeNewProject];
    p1.name = @"Proj1";

    NSLog(@"p1.name = %@, p1.personInCharge = %@", p1.name, p1.personInCharge.name);

    Worker *worker = p1.personInCharge;
    NSLog(@"w.project.name = %@", worker.project.name);

    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

@end

Usage

Add the code from Listings 10-31 through 10-38 to your app. If you have been following along with the previous recipes and want to reuse your Xcode project, make sure to delete the application from the iOS Simulator before attempting to test this code.

Build and run your application to see the following output in the console log:

p1.name = Proj1, p1.personInCharge = New Worker
w.project.name = Proj1

10.8 Using One-To-Many Relationships with Core Data

Problem

Your object graph requires you to represent a one-to-many relationship and you want this content managed by Core Data.

Solution

Create at least two entities in the data model and then add a one-to-many relationship between these entities in the data model editor.

How It Works

You’re getting closer to implementing the object graph from Recipe 9.1 in Core Data. What you want to do is add a task entity to your data model. Remember that the Task class from Recipe 9.1 has name, details, dueDate, and priority properties. Task also has a Worker property which you will leave for the next recipe. Tasks are contained in projects so the relationship is going to go from Project to Task. There will be many tasks for each project. You are going to just recreate the Project to Task relationship here.

NOTE: You are about to make another big change to your data model. Since the data model is cached after the first time it runs, you can’t change the data model without breaking the application. So you need to make sure to delete the application from the iOS Simulator before testing the changes that you are about to make to the data model. Go to the iOS Simulator and click iOS Simulator Image Reset Content and Settings. Click the Reset button that pops up.

Just like in Recipe 10.7, you are going to add another entity to the data model. This entity is called Task and the attributes will match the Task properties from Recipe 9.2. The Task entity description will look like Figure 10-7.

Image

Figure 10-7. Task entity

Now you are going to start to establish the relationship between Project and Task. Select the Project entity and click the plus sign in the Relationships pane in the data model editor. Name the relationship listOfTasks and set the Destination to Task.

To set up the inverse relationship, select the Task entity and click the plus sign in the Relationships pane in the data model editor. Name the relationship project and set the Destination to Project. Choose listOfTasks as the Inverse.

Select all three entities in the data model to see everything at once. Alternatively, you can also hold down the Shift key and then click the first and last entity to select all the entities. You should have something that looks like Figure 10-8.

Image

Figure 10-8. Project, Task, and Worker with relationships

To see the data model in a more visual way, you can change the editor style by clicking the segmented button in the bottom right hand area of the data model editor (the “Editor Styles: Table, Graph” button). This provides a graphical display that highlights the entities and their relationships. See Figure 10-9 for an example of what it looks like. You may need to move the entities around a little to get them to appear as they do in Figure 10-9.

Image

Figure 10-9. Visual editor style

You still need to make the Project to Task relationship a one-to-many relationship. To do so, select the listOfTasks relationship and use the data model inspector to change listOfTasks from a one-to-one relationship to a one-to-many relationship.

Select listOfTasks and then open the data model inspector, which is on the right hand side of the data model editor. Make sure that the right pane in Xcode is visible and that you have the data model inspector open (see Figure 10-10).

Image

Figure 10-10. Specifying one-to-many relationships

In the data inspector, click the checkbox named Plural that reads To-Many Relationship.

Now you are ready to create your managed objects. Make sure each entity is selected in the data model editor and then choose File Image New Image File. Then choose iOS Image Core Data Image NSManaged Object subclass. In the dialog that pops up, click Create. You need to allow Xcode to replace the file here.

Look at the Project interface to see how the one-to-many relationship is represented in code.

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class Worker;

@interface Project : NSManagedObject

@property (nonatomic, retain) NSString * descrip;
@property (nonatomic, retain) NSDate * dueDate;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) Worker *personInCharge;
@property (nonatomic, retain) NSSet *listOfTasks;
@end

@interface Project (CoreDataGeneratedAccessors)

- (void)addListOfTasksObject:(NSManagedObject *)value;
- (void)removeListOfTasksObject:(NSManagedObject *)value;
- (void)addListOfTasks:(NSSet *)values;
- (void)removeListOfTasks:(NSSet *)values;

@end

The property that holds the references to all your tasks is an NSSet named listOfTasks. The additional interface code is given to make it easier to add and remove items into the NSSet property. All you need to do to add Task objects into the project is to use these accessors. For example,

//Make a task
Task *t1 = (Task *)[NSEntityDescription insertNewObjectForEntityForName:@"Task" Image
inManagedObjectContext:[dataModel managedObjectContext]];

t1.name = @"Task 1";
t1.details = @"Task details";
t1.dueDate = [NSDate date];
t1.priority = [NSNumber numberWithInt:1];

//Add the task to the project
[p1 addListOfTasksObject:t1];

//Make a task
Task *t2 = (Task *)[NSEntityDescription insertNewObjectForEntityForName:@"Task" Image
inManagedObjectContext:[dataModel managedObjectContext]];

t2.name = @"Task 2";
t2.details = @"Task details";
t2.dueDate = [NSDate date];
t2.priority = [NSNumber numberWithInt:1];

//Add the task to the project
[p1 addListOfTasksObject:t2];

Now you can use the tasks that are associated with the project. To print these tasks out to the log, you could do something like this:

//Get all the projects in the data store
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Project"
                                    inManagedObjectContext:[dataModel Image
managedObjectContext]];

request.entity = entity;
NSArray *listOfProjects = [[dataModel managedObjectContext] Image
executeFetchRequest:request error:nil];

//Print out contents of all the projects (including the tasks)
NSLog(@"-----");
NSLog(@"NEW PROJECTS IN CONTEXT");
[listOfProjects enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    NSLog(@"project.name = %@", [obj name]);
    [[obj listOfTasks] enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
        NSLog(@" task.name = %@", [obj name]);
    }];
}];

See Listings 10-39 through 10-48.

The Code

Listing 10-39. AppDelegate.h

#import <UIKit/UIKit.h>
#import "AppModel.h"

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

Listing 10-40. AppDelegate.m

#import "AppDelegate.h"

@implementation AppDelegate
@synthesize window = _window;

- (BOOL)application:(UIApplication *)application Image
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

    //Create a new AppModel instance
    AppModel *dataModel = [[AppModel alloc] init];

    //Make a project
    Project *p1 = [dataModel makeNewProject];
    p1.name = @"Proj1";

    //Make a task
    Task *t1 = (Task *)[NSEntityDescription insertNewObjectForEntityForName:@"Task" Image
inManagedObjectContext:[dataModel managedObjectContext]];

    t1.name = @"Task 1";
    t1.details = @"Task details";
    t1.dueDate = [NSDate date];
    t1.priority = [NSNumber numberWithInt:1];

    //Add the task to the project
    [p1 addListOfTasksObject:t1];

    //Make a task
    Task *t2 = (Task *)[NSEntityDescription insertNewObjectForEntityForName:@"Task"
                                                     inManagedObjectContext:  Image
[dataModel managedObjectContext]];

    t2.name = @"Task 2";
    t2.details = @"Task details";
    t2.dueDate = [NSDate date];
    t2.priority = [NSNumber numberWithInt:1];

    //Add the task to the project
    [p1 addListOfTasksObject:t2];

    //Get all the projects in the data store
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Project"
                                              inManagedObjectContext:[dataModel Image
managedObjectContext]];

    request.entity = entity;
    NSArray *listOfProjects = [[dataModel managedObjectContext] Image
executeFetchRequest:request error:nil];

    //print out contents of all the projects (including the tasks):
    NSLog(@"-----");
    NSLog(@"NEW PROJECTS IN CONTEXT");
    [listOfProjects enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSLog(@"project.name = %@", [obj name]);
        [[obj listOfTasks] enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
            NSLog(@" task.name = %@", [obj name]);
        }];
    }];

    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

@end

Listing 10-41. AppModel.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "Project.h"
#import "Worker.h"
#import "Task.h"

@interface AppModel : NSObject

-(NSURL *)dataStoreURL;

@property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, strong, readonly) NSPersistentStoreCoordinator Image
*persistentStoreCoordinator;
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;

-(Project *)makeNewProject;
-(Worker *)makeNewWorker;
-(Task *)makeNewTask;

@end

Listing 10-42. AppModel.m

#import "AppModel.h"

@implementation AppModel
NSManagedObjectModel *_managedObjectModel;
NSPersistentStoreCoordinator *_persistentStoreCoordinator;
NSManagedObjectContext *_managedObjectContext;

-(Project *)makeNewProject{

    Project *managedProject = (Project *)[NSEntityDescription Image
insertNewObjectForEntityForName:@"Project" Image
inManagedObjectContext:[self managedObjectContext]];

    managedProject.name = @"New Project";
    managedProject.descrip = @"This is a new project";
    managedProject.dueDate = [NSDate date];

    managedProject.personInCharge = [self makeNewWorker];

    return managedProject;

}

-(Worker *)makeNewWorker{
    Worker *managedWorker = (Worker *)[NSEntityDescription Image
insertNewObjectForEntityForName:@"Worker" Image
inManagedObjectContext:[self managedObjectContext]];

    managedWorker.name = @"New Worker";
    managedWorker.Role = @"Works on projects";

    return managedWorker;
}

-(Task *)makeNewTask{
    Task *managedTask = (Task *)[NSEntityDescription Image
insertNewObjectForEntityForName:@"Task" Image
inManagedObjectContext:[self managedObjectContext]];

    managedTask.name = @"New Task";
    managedTask.details = @"Task details";
    managedTask.dueDate = [NSDate date];
    managedTask.priority = [NSNumber numberWithInt:1];

    return managedTask;
}

- (NSURL *)dataStoreURL {

    NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, Image
NSUserDomainMask, YES) lastObject];

    return [NSURL fileURLWithPath:[docDir Image
stringByAppendingPathComponent:@"DataStore.sql"]];
}

- (NSManagedObjectModel *)managedObjectModel {
    if (_managedObjectModel) {
        return _managedObjectModel;
    }
    _managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
    return _managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (_persistentStoreCoordinator) {
        return _persistentStoreCoordinator;
    }

    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] Image
initWithManagedObjectModel:[self managedObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                   configuration:nil
                                                             URL:[self dataStoreURL]
                                                         options:nil
                                                           error:&error]) {
        NSLog(@"Unresolved Core Data error with persistentStoreCoordinator: %@, %@", Image
error, [error userInfo]);
    }

    return _persistentStoreCoordinator;
}

- (NSManagedObjectContext *)managedObjectContext {
    if (_managedObjectContext) {
        return _managedObjectContext;
    }

    if ([self persistentStoreCoordinator]) {
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:[self Image
persistentStoreCoordinator]];
    }

    return _managedObjectContext;
}

@end

Listing 10-43. Project.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class Worker;

@interface Project : NSManagedObject

@property (nonatomic, retain) NSString * descrip;
@property (nonatomic, retain) NSDate * dueDate;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) Worker *personInCharge;
@property (nonatomic, retain) NSSet *listOfTasks;
@end

@interface Project (CoreDataGeneratedAccessors)

- (void)addListOfTasksObject:(NSManagedObject *)value;
- (void)removeListOfTasksObject:(NSManagedObject *)value;
- (void)addListOfTasks:(NSSet *)values;
- (void)removeListOfTasks:(NSSet *)values;

@end

Listing 10-44. Project.m

#import "Project.h"
#import "Worker.h"


@implementation Project

@dynamic descrip;
@dynamic dueDate;
@dynamic name;
@dynamic personInCharge;
@dynamic listOfTasks;

@end

Listing 10-45. Worker.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class Project;

@interface Worker : NSManagedObject

@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSString * role;
@property (nonatomic, retain) Project *project;

@end

Listing 10-46. Worker.m

#import "Worker.h"
#import "Project.h"

@implementation Worker

@dynamic name;
@dynamic role;
@dynamic project;

@end

Listing 10-47. Task.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class Project;

@interface Task : NSManagedObject

@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSString * details;
@property (nonatomic, retain) NSString * dueDate;
@property (nonatomic, retain) NSNumber * priority;
@property (nonatomic, retain) Project *project;

@end

Listing 10-48. Task.m

#import "Task.h"
#import "Project.h"

@implementation Task

@dynamic name;
@dynamic details;
@dynamic dueDate;
@dynamic priority;
@dynamic project;

@end

Usage

To use this code, add the entities as described in the “How It Works” section. Include the code from Listings 10-39 through 10-48 for the AppDelegate and the AppModel classes. Build and run your project and you should see output that looks like this:

-----
 NEW PROJECTS IN CONTEXT
 project.name = Proj1
  task.name = Task 1
  task.name = Task 2

10.9 Managing Data Store Versioning

Problem

You have an application that is already deployed to your customers and you want to make a change to the data model. You know if you just make the change to the existing data model you will break your user’s application.

Solution

Add a new version of your data model to your application based on your original data model. Set the new version of your data model to be the current model used by the application. Finally, add some options to your persistent store coordinator to make sure that the updated data model is used.

How It Works

As you probably realize by now, if you are developing an application and then decide to make a change to the data model, your application will crash when you try to test your code. This is because the application is trying to use the managed object model that was created during the first run with an updated managed object model that you just created.

Normally you can just delete your application from the iOS Simulator (or Mac desktop) and start over without any problems. However, if you have people already using your application, you need to make sure that you can use the new data model version without breaking their application or losing their content.

To demonstrate this recipe, go back to the application created in Recipe 10.8 and add a new relationship to the task entity. In the original object graph from Recipe 9.1, the Task class had a one-to-one relationship with the Worker class. Add this relationship now with a new version of the data model.

First, add some options to the persistent store coordinator. Set two flags to YES to allow automatic versioning. These flags are NSMigratePersistentStoresAutomaticallyOption and NSInferMappingModelAutomaticallyOption; to use them you must put them both in an NSDictionary object with their values set to YES. You add this update to the persistent store coordinator in the AppModel.m file where you have the persistent store coordinator coded.

#import "AppModel.h"

@implementation AppModel

...

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (_persistentStoreCoordinator) {
        return _persistentStoreCoordinator;
    }

    NSError *error = nil;

    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: Image
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, Image
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] Image
initWithManagedObjectModel:[self managedObjectModel]];

    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                   configuration:nil
                                                             URL:[self dataStoreURL]
                                                         options:options
                                                           error:&error]) {
        NSLog(@"Unresolved Core Data error with persistentStoreCoordinator: %@, %@", Image
error, [error userInfo]);
    }

    return _persistentStoreCoordinator;
}

...

@end

To make the change a bit clearer, I’ve highlighted the additional code in bold.

Now you can go ahead and create a new version of the data model based on the original. Select your data model, which is the file named Model.xcdatamodeld. Then go to Editor Image Add Model Version. Name your version Model 2 and select Model in the “Based on Model” drop-down box.

When you look at your data model file, you will see that there are two data model files, Model.xcdatamodeld and Model 2.xcdatamodeld. Right now, they are identical and you can see each data model by clicking on the respective file.

Now set the current data model to Model 2. This is how you let Core Data know that you are using the new version. You do this by selecting the top level in the data model. Make sure that the right pane is showing the File Inspector. Locate the drop-down box labeled Current. Select Model 2 from the Current drop-down box (see Figure 10-11).

Image

Figure 10-11. Setting the current data model versions

Now add the Task to Worker one-to-one relationship. First, select Model 2 so you know that you’re working on the new version. To establish the relationship, select Task in the data model editor and then click the plus button in the Relationships pane of the data model editor. Name the relationship assignedTo and set the Destination to Worker.

Now you need to define the inverse (or opposite) relationship. This gives you a way to reference the task that a worker is working on. Select the Worker entity and click the plus button in the Relationships pane of the data model editor. Name the relationship task and set the Destination to Task. Select assignedTo for the Inverse.

To see everything that you just did at one time, select each entity in the data model editor while holding down the Command key. Both entities will be highlighted and you will see all the attributes and relationships listed at once. Your data model editor should look like Figure 10-12.

Image

Figure 10-12. Task to Worker and Worker to Task relationship

Keeping all the entities highlighted, go to File Image New Image File. Then choose iOS Image Core Data Image NSManagedObject subclass. Click Next and then Create. You will get a warning dialog because you are going to write over the previous Project class file. That’s ok since you do need to update it, so click Replace.

Now you can use your Core Data without breaking your application.

//Create a new AppModel instance
AppModel *dataModel = [[AppModel alloc] init];

//Make a project
Project *p1 = [dataModel makeNewProject];
p1.name = @"Proj1";

//Make a task
Task *t1 = (Task *)[NSEntityDescription insertNewObjectForEntityForName:@"Task"

inManagedObjectContext:[dataModel managedObjectContext]];
t1.name = @"Task 1";
t1.details = @"Task details";
t1.dueDate = [NSDate date];
t1.priority = [NSNumber numberWithInt:1];

//Assign a worker to this task:
Worker *managedWorker = (Worker *)[NSEntityDescription
insertNewObjectForEntityForName:@"Worker"

inManagedObjectContext:[dataModel managedObjectContext]];
managedWorker.name = @"John";
managedWorker.Role = @"Programmer";

t1.assignedTo = managedWorker;

Core Data will take care of managing the two versions for each user’s application without any more intervention from you. See Listings 10-49 through 10-58.

The Code

Listing 10-49. AppDelegate.h

#import <UIKit/UIKit.h>
#import "AppModel.h"

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

Listing 10-50. AppDelegate.m

#import "AppDelegate.h"

@implementation AppDelegate
@synthesize window = _window;

- (BOOL)application:(UIApplication *)application Image
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

    //Create a new AppModel instance
    AppModel *dataModel = [[AppModel alloc] init];

    //Make a project
    Project *p1 = [dataModel makeNewProject];
    p1.name = @"Proj1";

    //Make a task
    Task *t1 = (Task *)[NSEntityDescription insertNewObjectForEntityForName:@"Task" Image
inManagedObjectContext:[dataModel managedObjectContext]];

    t1.name = @"Task 1";
    t1.details = @"Task details";
    t1.dueDate = [NSDate date];
    t1.priority = [NSNumber numberWithInt:1];


    //Assign a worker to this task:
    Worker *managedWorker = (Worker *)[NSEntityDescription Image
insertNewObjectForEntityForName:@"Worker" Image
inManagedObjectContext:[dataModel managedObjectContext]];

    managedWorker.name = @"John";
    managedWorker.Role = @"Programmer";

    t1.assignedTo = managedWorker;

    //Add the task to the project
    [p1 addListOfTasksObject:t1];

    //Make a task
    Task *t2 = (Task *)[NSEntityDescription insertNewObjectForEntityForName:@"Task"
                                                     inManagedObjectContext:  Image
[dataModel managedObjectContext]];

    t2.name = @"Task 2";
    t2.details = @"Task details";
    t2.dueDate = [NSDate date];
    t2.priority = [NSNumber numberWithInt:1];

    //Add the task to the project
    [p1 addListOfTasksObject:t2];

    //Get all the projects in the data store
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Project"
                                              inManagedObjectContext:[dataModel Image
managedObjectContext]];

    request.entity = entity;
    NSArray *listOfProjects = [[dataModel managedObjectContext] Image
executeFetchRequest:request error:nil];

    //print out contents of all the projects (including the tasks):
    NSLog(@"-----");
    NSLog(@"NEW PROJECTS IN CONTEXT");
    [listOfProjects enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSLog(@"project.name = %@", [obj name]);
        [[obj listOfTasks] enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
            NSLog(@" task.name = %@", [obj name]);
            NSLog(@" task.assignedTo = %@", [[obj assignedTo] name]);
        }];
    }];

    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

@end

Listing 10-51. AppModel.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "Project.h"
#import "Worker.h"
#import "Task.h"

@interface AppModel : NSObject

-(NSURL *)dataStoreURL;

@property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, strong, readonly) NSPersistentStoreCoordinator Image
*persistentStoreCoordinator;
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;

-(Project *)makeNewProject;
-(Worker *)makeNewWorker;
-(Task *)makeNewTask;

@end

Listing 10-52. AppModel.m

#import "AppModel.h"

@implementation AppModel
NSManagedObjectModel *_managedObjectModel;
NSPersistentStoreCoordinator *_persistentStoreCoordinator;
NSManagedObjectContext *_managedObjectContext;

-(Project *)makeNewProject{

    Project *managedProject = (Project *)[NSEntityDescription Image
insertNewObjectForEntityForName:@"Project" Image
inManagedObjectContext:[self managedObjectContext]];

    managedProject.name = @"New Project";
    managedProject.descrip = @"This is a new project";
    managedProject.dueDate = [NSDate date];

    managedProject.personInCharge = [self makeNewWorker];

    return managedProject;

}

-(Worker *)makeNewWorker{
    Worker *managedWorker = (Worker *)[NSEntityDescription Image
insertNewObjectForEntityForName:@"Worker" Image
inManagedObjectContext:[self managedObjectContext]];

    managedWorker.name = @"New Worker";
    managedWorker.Role = @"Works on projects";

    return managedWorker;
}

-(Task *)makeNewTask{
    Task *managedTask = (Task *)[NSEntityDescription Image
insertNewObjectForEntityForName:@"Task" Image
inManagedObjectContext:[self managedObjectContext]];

    managedTask.name = @"New Task";
    managedTask.details = @"Task details";
    managedTask.dueDate = [NSDate date];
    managedTask.priority = [NSNumber numberWithInt:1];

    return managedTask;
}

- (NSURL *)dataStoreURL {

    NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, Image
NSUserDomainMask, YES) lastObject];

    return [NSURL fileURLWithPath:[docDir Image
stringByAppendingPathComponent:@"DataStore.sql"]];
}

- (NSManagedObjectModel *)managedObjectModel {
    if (_managedObjectModel) {
        return _managedObjectModel;
    }
    _managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
    return _managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    if (_persistentStoreCoordinator) {
        return _persistentStoreCoordinator;
    }

    NSError *error = nil;
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: Image
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, Image
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] Image
initWithManagedObjectModel:[self managedObjectModel]];

if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                               configuration:nil
                                                         URL:[self dataStoreURL]
                                                     options:options
                                                       error:&error]) {
    NSLog(@"Unresolved Core Data error with persistentStoreCoordinator: %@, %@", Image
error, [error userInfo]);
}
    return _persistentStoreCoordinator;
}

- (NSManagedObjectContext *)managedObjectContext {
    if (_managedObjectContext) {
        return _managedObjectContext;
    }

    if ([self persistentStoreCoordinator]) {
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:[self Image
persistentStoreCoordinator]];
    }

    return _managedObjectContext;
}

@end

Listing 10-53. Project.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class Worker;

@interface Project : NSManagedObject

@property (nonatomic, retain) NSString * descrip;
@property (nonatomic, retain) NSDate * dueDate;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) Worker *personInCharge;
@property (nonatomic, retain) NSSet *listOfTasks;
@end

@interface Project (CoreDataGeneratedAccessors)

- (void)addListOfTasksObject:(NSManagedObject *)value;
- (void)removeListOfTasksObject:(NSManagedObject *)value;
- (void)addListOfTasks:(NSSet *)values;
- (void)removeListOfTasks:(NSSet *)values;

@end

Listing 10-54. Project.m

#import "Project.h"
#import "Worker.h"


@implementation Project

@dynamic descrip;
@dynamic dueDate;
@dynamic name;
@dynamic personInCharge;
@dynamic listOfTasks;

@end

Listing 10-55. Task.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class Project, Worker;

@interface Task : NSManagedObject

@property (nonatomic, retain) NSString * details;
@property (nonatomic, retain) NSDate * dueDate;
@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSNumber * priority;
@property (nonatomic, retain) Project *project;
@property (nonatomic, retain) Worker *assignedTo;

@end

Listing 10-56. Task.m

#import "Task.h"
#import "Project.h"
#import "Worker.h"


@implementation Task

@dynamic details;
@dynamic dueDate;
@dynamic name;
@dynamic priority;
@dynamic project;
@dynamic assignedTo;

@end

Listing 10-57. Worker.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class Project, Task;

@interface Worker : NSManagedObject

@property (nonatomic, retain) NSString * name;
@property (nonatomic, retain) NSString * role;
@property (nonatomic, retain) Project *project;
@property (nonatomic, retain) Task *task;

@end

Listing 10-58. Worker.m

#import "Worker.h"
#import "Project.h"
#import "Task.h"


@implementation Worker

@dynamic name;
@dynamic role;
@dynamic project;
@dynamic task;

@end

Usage

Versioning is a little tricky to test out. Start with the application from Recipe 10.8 and make sure to build it so that you can see the output in the console log. Then go through the process of following this recipe to see if you can update the data model gracefully. After you build and run this application you should see something like this appear in your console log:

-----
 NEW PROJECTS IN CONTEXT
 project.name = Proj1
  task.name = Task 1
  task.assignedTo = John
  task.name = Task 2
  task.assignedTo = (null)
..................Content has been hidden....................

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