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:
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.
You want to add Core Data support to your iOS or Mac application.
Link to the Core Data framework and add the Core Data stack to the class that you would like to support Core Data.
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.
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.
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.
Select the item named CoreData.framework
and click the Add button.
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,
NSUserDomainMask, YES) lastObject];
return [NSURL fileURLWithPath:[docDir
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]
initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[self dataStoreURL]
options:nil
error:&error]) {
NSLog(@"Unresolved Core Data error with persistentStoreCoordinator: %@, %@",
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
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.
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@interface AppModel : NSObject
-(NSURL *)dataStoreURL;
@property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, strong, readonly) NSPersistentStoreCoordinator
*persistentStoreCoordinator;
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;
@end
#import "AppModel.h"
@implementation AppModel
NSManagedObjectModel *_managedObjectModel;
NSPersistentStoreCoordinator *_persistentStoreCoordinator;
NSManagedObjectContext *_managedObjectContext;
- (NSURL *)dataStoreURL {
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
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]
initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[self dataStoreURL]
options:nil
error:&error]) {
NSLog(@"Unresolved Core Data error with persistentStoreCoordinator: %@, %@",
error, [error userInfo]);
}
return _persistentStoreCoordinator;
}
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext) {
return _managedObjectContext;
}
if ([self persistentStoreCoordinator]) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:[self
persistentStoreCoordinator]];
}
return _managedObjectContext;
}
@end
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
#import "AppDelegate.h"
@implementation AppDelegate
@synthesize window = _window;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
@end
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.
You need to describe the entity that will be managed by Core Data.
Add a data model file to your application and then use the data model editor to describe your entity.
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 New File. Then choose iOS Core Data 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.
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.
That’s all there is to using the data model editor to describe an entity.
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.
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
#import "AppDelegate.h"
@implementation AppDelegate
@synthesize window = _window;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
@end
Now you’re ready to move on to Recipe 10.3 and continue setting up Core Data.
The entity that you composed in Recipe 10.2 needs an Objective-C class so that you can use it in code.
Use Xcode to automatically generate a code file based on the entity description that you set up in Recipe 10.2.
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 New File. Then choose iOS Core Data 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.
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
#import "AppDelegate.h"
@implementation AppDelegate
@synthesize window = _window;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
@end
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
@interface AppModel : NSObject
-(NSURL *)dataStoreURL;
@property (nonatomic, strong, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, strong, readonly) NSPersistentStoreCoordinator
*persistentStoreCoordinator;
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;
@end
#import "AppModel.h"
@implementation AppModel
NSManagedObjectModel *_managedObjectModel;
NSPersistentStoreCoordinator *_persistentStoreCoordinator;
NSManagedObjectContext *_managedObjectContext;
- (NSURL *)dataStoreURL {
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
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]
initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[self dataStoreURL]
options:nil
error:&error]) {
NSLog(@"Unresolved Core Data error with persistentStoreCoordinator: %@, %@",
error, [error userInfo]);
}
return _persistentStoreCoordinator;
}
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext) {
return _managedObjectContext;
}
if ([self persistentStoreCoordinator]) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:[self
persistentStoreCoordinator]];
}
return _managedObjectContext;
}
@end
#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
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.
You want to use objects that are managed by Core Data.
Use the managed object context to create a new managed object and save the managed object to the data store.
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
insertNewObjectForEntityForName:@"Project"
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.
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.
#import <UIKit/UIKit.h>
#import "AppModel.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
#import "AppDelegate.h"
@implementation AppDelegate
@synthesize window = _window;
- (BOOL)application:(UIApplication *)application 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];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
@end
#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
*persistentStoreCoordinator;
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;
@end
#import "AppModel.h"
@implementation AppModel
NSManagedObjectModel *_managedObjectModel;
NSPersistentStoreCoordinator *_persistentStoreCoordinator;
NSManagedObjectContext *_managedObjectContext;
-(Project *)makeNewProject{
Project *managedProject = (Project *)[NSEntityDescription
insertNewObjectForEntityForName:@"Project"
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,
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]
initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[self dataStoreURL]
options:nil
error:&error]) {
NSLog(@"Unresolved Core Data error with persistentStoreCoordinator: %@, %@",
error, [error userInfo]);
}
return _persistentStoreCoordinator;
}
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext) {
return _managedObjectContext;
}
if ([self persistentStoreCoordinator]) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:[self
persistentStoreCoordinator]];
}
return _managedObjectContext;
}
@end
#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
#import "Project.h"
@implementation Project
@dynamic descrip;
@dynamic dueDate;
@dynamic name;
@end
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.
You want users to retrieve objects from the data store that they have worked on earlier.
Use a fetch request to retrieve objects that are already in the data store.
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
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
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.
#import <UIKit/UIKit.h>
#import "AppModel.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
#import "AppDelegate.h"
@implementation AppDelegate
@synthesize window = _window;
- (BOOL)application:(UIApplication *)application
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
managedObjectContext]];
request.entity = entity;
NSArray *listOfProjects = [[dataModel managedObjectContext]
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
#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
*persistentStoreCoordinator;
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;
@end
#import "AppModel.h"
@implementation AppModel
NSManagedObjectModel *_managedObjectModel;
NSPersistentStoreCoordinator *_persistentStoreCoordinator;
NSManagedObjectContext *_managedObjectContext;
-(Project *)makeNewProject{
Project *managedProject = (Project *)[NSEntityDescription
insertNewObjectForEntityForName:@"Project"
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,
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]
initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[self dataStoreURL]
options:nil
error:&error]) {
NSLog(@"Unresolved Core Data error with persistentStoreCoordinator: %@, %@",
error, [error userInfo]);
}
return _persistentStoreCoordinator;
}
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext) {
return _managedObjectContext;
}
if ([self persistentStoreCoordinator]) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:[self
persistentStoreCoordinator]];
}
return _managedObjectContext;
}
@end
#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
#import "Project.h"
@implementation Project
@dynamic descrip;
@dynamic dueDate;
@dynamic name;
@end
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.
As your users work with your application they makes changes to the content that you want to save to the data store.
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.
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.
#import <UIKit/UIKit.h>
#import "AppModel.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
#import "AppDelegate.h"
@implementation AppDelegate
@synthesize window = _window;
- (BOOL)application:(UIApplication *)application
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
managedObjectContext]];
request.entity = entity;
NSArray *listOfProjects = [[dataModel managedObjectContext]
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
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
#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
*persistentStoreCoordinator;
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;
@end
#import "AppModel.h"
@implementation AppModel
NSManagedObjectModel *_managedObjectModel;
NSPersistentStoreCoordinator *_persistentStoreCoordinator;
NSManagedObjectContext *_managedObjectContext;
-(Project *)makeNewProject{
Project *managedProject = (Project *)[NSEntityDescription
insertNewObjectForEntityForName:@"Project"
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,
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]
initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[self dataStoreURL]
options:nil
error:&error]) {
NSLog(@"Unresolved Core Data error with persistentStoreCoordinator: %@, %@",
error, [error userInfo]);
}
return _persistentStoreCoordinator;
}
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext) {
return _managedObjectContext;
}
if ([self persistentStoreCoordinator]) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:[self
persistentStoreCoordinator]];
}
return _managedObjectContext;
}
@end
#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
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.
Your object graph requires you to represent a one-to-one relationship and you want this content managed by Core Data.
Create at least two entities in the data model and then add a relationship between these entities in the data model editor.
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 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.
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.
Keeping both the Project
and Worker
entities highlighted, go to File New File. Then choose iOS Core Data 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
*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
insertNewObjectForEntityForName:@"Worker"
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
insertNewObjectForEntityForName:@"Project"
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.
#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
#import "AppModel.h"
@implementation AppModel
NSManagedObjectModel *_managedObjectModel;
NSPersistentStoreCoordinator *_persistentStoreCoordinator;
NSManagedObjectContext *_managedObjectContext;
-(Project *)makeNewProject{
Project *managedProject = (Project *)[NSEntityDescription
insertNewObjectForEntityForName:@"Project"
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
insertNewObjectForEntityForName:@"Worker"
inManagedObjectContext:[self managedObjectContext]];
managedWorker.name = @"New Worker";
managedWorker.Role = @"Works on projects";
return managedWorker;
}
- (NSURL *)dataStoreURL {
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
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]
initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[self dataStoreURL]
options:nil
error:&error]) {
NSLog(@"Unresolved Core Data error with persistentStoreCoordinator: %@, %@",
error, [error userInfo]);
}
return _persistentStoreCoordinator;
}
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext) {
return _managedObjectContext;
}
if ([self persistentStoreCoordinator]) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:[self
persistentStoreCoordinator]];
}
return _managedObjectContext;
}
@end
#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
#import "Worker.h"
#import "Project.h"
@implementation Worker
@dynamic name;
@dynamic role;
@dynamic project;
@end
#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
#import "Project.h"
#import "Worker.h"
@implementation Project
@dynamic descrip;
@dynamic dueDate;
@dynamic name;
@dynamic personInCharge;
@end
#import <UIKit/UIKit.h>
#import "AppModel.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
#import "AppDelegate.h"
@implementation AppDelegate
@synthesize window = _window;
- (BOOL)application:(UIApplication *)application
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
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
Your object graph requires you to represent a one-to-many relationship and you want this content managed by Core Data.
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.
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 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.
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.
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.
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).
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 New File. Then choose iOS Core Data 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"
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:[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
managedObjectContext]];
request.entity = entity;
NSArray *listOfProjects = [[dataModel managedObjectContext]
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.
#import <UIKit/UIKit.h>
#import "AppModel.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
#import "AppDelegate.h"
@implementation AppDelegate
@synthesize window = _window;
- (BOOL)application:(UIApplication *)application
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"
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:
[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
managedObjectContext]];
request.entity = entity;
NSArray *listOfProjects = [[dataModel managedObjectContext]
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
#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
*persistentStoreCoordinator;
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;
-(Project *)makeNewProject;
-(Worker *)makeNewWorker;
-(Task *)makeNewTask;
@end
#import "AppModel.h"
@implementation AppModel
NSManagedObjectModel *_managedObjectModel;
NSPersistentStoreCoordinator *_persistentStoreCoordinator;
NSManagedObjectContext *_managedObjectContext;
-(Project *)makeNewProject{
Project *managedProject = (Project *)[NSEntityDescription
insertNewObjectForEntityForName:@"Project"
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
insertNewObjectForEntityForName:@"Worker"
inManagedObjectContext:[self managedObjectContext]];
managedWorker.name = @"New Worker";
managedWorker.Role = @"Works on projects";
return managedWorker;
}
-(Task *)makeNewTask{
Task *managedTask = (Task *)[NSEntityDescription
insertNewObjectForEntityForName:@"Task"
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,
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]
initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[self dataStoreURL]
options:nil
error:&error]) {
NSLog(@"Unresolved Core Data error with persistentStoreCoordinator: %@, %@",
error, [error userInfo]);
}
return _persistentStoreCoordinator;
}
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext) {
return _managedObjectContext;
}
if ([self persistentStoreCoordinator]) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:[self
persistentStoreCoordinator]];
}
return _managedObjectContext;
}
@end
#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
#import "Project.h"
#import "Worker.h"
@implementation Project
@dynamic descrip;
@dynamic dueDate;
@dynamic name;
@dynamic personInCharge;
@dynamic listOfTasks;
@end
#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
#import "Worker.h"
#import "Project.h"
@implementation Worker
@dynamic name;
@dynamic role;
@dynamic project;
@end
#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
#import "Task.h"
#import "Project.h"
@implementation Task
@dynamic name;
@dynamic details;
@dynamic dueDate;
@dynamic priority;
@dynamic project;
@end
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
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.
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.
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:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[self dataStoreURL]
options:options
error:&error]) {
NSLog(@"Unresolved Core Data error with persistentStoreCoordinator: %@, %@",
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 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).
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.
Keeping all the entities highlighted, go to File New File. Then choose iOS Core Data 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.
#import <UIKit/UIKit.h>
#import "AppModel.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end
#import "AppDelegate.h"
@implementation AppDelegate
@synthesize window = _window;
- (BOOL)application:(UIApplication *)application
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"
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;
//Add the task to the project
[p1 addListOfTasksObject:t1];
//Make a task
Task *t2 = (Task *)[NSEntityDescription insertNewObjectForEntityForName:@"Task"
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];
//Get all the projects in the data store
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Project"
inManagedObjectContext:[dataModel
managedObjectContext]];
request.entity = entity;
NSArray *listOfProjects = [[dataModel managedObjectContext]
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
#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
*persistentStoreCoordinator;
@property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;
-(Project *)makeNewProject;
-(Worker *)makeNewWorker;
-(Task *)makeNewTask;
@end
#import "AppModel.h"
@implementation AppModel
NSManagedObjectModel *_managedObjectModel;
NSPersistentStoreCoordinator *_persistentStoreCoordinator;
NSManagedObjectContext *_managedObjectContext;
-(Project *)makeNewProject{
Project *managedProject = (Project *)[NSEntityDescription
insertNewObjectForEntityForName:@"Project"
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
insertNewObjectForEntityForName:@"Worker"
inManagedObjectContext:[self managedObjectContext]];
managedWorker.name = @"New Worker";
managedWorker.Role = @"Works on projects";
return managedWorker;
}
-(Task *)makeNewTask{
Task *managedTask = (Task *)[NSEntityDescription
insertNewObjectForEntityForName:@"Task"
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,
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;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[self dataStoreURL]
options:options
error:&error]) {
NSLog(@"Unresolved Core Data error with persistentStoreCoordinator: %@, %@",
error, [error userInfo]);
}
return _persistentStoreCoordinator;
}
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext) {
return _managedObjectContext;
}
if ([self persistentStoreCoordinator]) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:[self
persistentStoreCoordinator]];
}
return _managedObjectContext;
}
@end
#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
#import "Project.h"
#import "Worker.h"
@implementation Project
@dynamic descrip;
@dynamic dueDate;
@dynamic name;
@dynamic personInCharge;
@dynamic listOfTasks;
@end
#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
#import "Task.h"
#import "Project.h"
#import "Worker.h"
@implementation Task
@dynamic details;
@dynamic dueDate;
@dynamic name;
@dynamic priority;
@dynamic project;
@dynamic assignedTo;
@end
#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
#import "Worker.h"
#import "Project.h"
#import "Task.h"
@implementation Worker
@dynamic name;
@dynamic role;
@dynamic project;
@dynamic task;
@end
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)
18.223.206.69