I'm trying to learn about Core Data and am following the "Locations" tutorial from Apple. One immediate stumbling block i've come across is that I have already started constructing the application that i want to use Core Data with. It is a single view application.
What are the steps I need to take in order to utilize Core Data with this application? The tutorial says to select the checkbox "use core data for storage" when creating the project but there must be a way to enable core data after the project creation.
I'd really appreciate some help. Thanks.
1.) Create a View-based Application named CoreDataTutorial.
2.) Add the core data framework to the project. Right click on Frameworks, select Add > Existing Frameworks … find CoreData.frameworks and click add.
3.) Add a Data Model to the project. Right click on Resources, select Add > New File … under iOS choose Resource and then select Data Model and hit Next.
Name the file CoreDataTutorial.xcdatamodel, and hit next.
4.) Double click on the file we just created, CoreDataTutorial.xcdatamodel. This opens up the model object editor.
In the Top left pane click the + symbol to add a new Entity.
Name the entity “SomeName” by entering the name in the top right pane.
While the entity is still selected, click the + symbol in the top middle pane and choose Add Attribute.Name this Attribute “some_attribute_name” and set it to type String.
5.) Now we are going to create relationships between our two entities. Select your entity in the entity pane. Click the + symbol in the property pane and select Add Relationship. Name the relationship “creation”, set the Destination to Release and the Delete Rule to Cascade.
To do the inverse we select Release in the entity pane. Click the + symbol in the property pane and select Add Relationship. Name the relationship “creator”, set the Destination to Artist, set the Inverse to release and the Delete Rule to Cascade.
You can now close the object editor.
6.) Expand Other Sources and double click CoreDataTutorial_Prefix.pch. Add an import for CoreData.
#ifdef __OBJC__
#import <foundation foundation.h="">
#import <uikit uikit.h="">
#import <coredata coredata.h="">
#endif
This saves us from having to import it into each file.
7.) Next we are going to set up the app delegate header file and then the implementation file.
First the header file. We need to create variables for our NSManagedObjectContext, the NSManagedObjectModel, and the NSPersistentStoreCoordinator.
We are also going to declare an action named applicationDocumentsDirectory, this action gets the path to the condiments directory where our data will be stored in a SQLite file. And an action that saves the context when the app quits.
Here’s what the header file looks like when we’re done. Remember we added the import statement to the CoreDataTutorial_Prefix.pch file so we don’t need to import it here.
#import <uikit uikit.h="">
#class CoreDataTutorialViewController;
#interface CoreDataTutorialAppDelegate : NSObject <uiapplicationdelegate>
{
UIWindow *window;
CoreDataTutorialViewController *viewController;
#private
NSManagedObjectContext *managedObjectContext;
NSManagedObjectModel *managedObjectModel;
NSPersistentStoreCoordinator *persistentStoreCoordinator;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet CoreDataTutorialViewController *viewController;
#property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
#property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (NSURL *)applicationDocumentsDirectory;
- (void)saveContext;
#end
8.) If you are not using an ARC,take care about deallocating memory
9.) Implement applicationDocumentsDirectory method.
/**
Returns the URL to the application's Documents directory.
*/
- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
10.) Next, implement saveContext method:
- (void)saveContext
{
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil)
{
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
{
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
}
11.) Finally implement your accessors methods for your variables and that's it.
Go to Targets -> Build Phases -> Link Binary with libraries and add CoreData.framework. You should be good to go...
Of course you can add core data support after you already started or created you app.
I would recommend you to do the following:
Add core data framework to your target
Add a object model "Add new File -> Core Data -> Data Model"
Create your initial object model
Create NSManagedObject Subclasses for all your models in your object model
Create a Controller (subclass NSObject) where you do a singleton / static instance (singleton)
In your controller add some data-layer method like "getObjectsXYZ", "saveData", etc.
In your existing app you can then load objects through your controller class like "[[mycontroller sharedInstance] getObjectYByName:#"some text"]"
Of course you need to dig into core data. :)
If you used to ORM's then you might look at: mogenerator vim rentsch.
http://rentzsch.github.com/mogenerator/
This makes data models very very easy. I swear on that tool!
If you need to see the kind of code additions that you'll need to make to access Core Data, create a dummy project that includes Core Data and look at the kind of methods that are in the app delegate to support it.
Or, you can go with a nicer approach of having your Core Data access code in its own class. A good description of that strategy can be found here: http://nachbaur.com/blog/smarter-core-data
Honestly, if you only have a single view it's probably easiest just to create the application from scratch as the tutorial says. There are quite a few bits and pieces that you need to include and it could save you a lot of time in the long run.
However, you can do it by hand:
Add the Core Data framework to your project
Add a data model
Add code in your app delegate to create the managed object context (including all the error conditions)
From my experience the better way to include Core Data in your project is to select a project type that has an option 'Use Core Data for storage'. You can include files from your existing project in the new one. Of course you can include Core Data manually, but it will create a lot of problems.
Related
I am building an application that is constantly speaking to a webservice. So posting and getting data all the time. But all the data that I post and get should also be saved on the phone.
Problem 1
When I was looking through the examples. I saw that they are loading all the data in the appDelegate. For loading in small amount of data this is probably the best way to do it. But in my case, is it still the best way or should I do this on ViewController Level.
Problem 2
When I started the application I checked use core data this generated a lot of code for me in the appDelegate. But in this case I can't get to my managedObjectContext on viewController Level, right?
My question is now, what is the best way to get this properly done?
Kind regards
Problem 1
When I was looking through the examples. I saw that they are loading all the data in the appDelegate. For loading in small amount of data this is probably the best way to do it. But in my case, is it still the best way or should I do this on ViewController Level.
The use of an appDelegate for that is typical of sample code.
How to best deal with that depends strictly on your app. Encapsulating data transfer into your view controller is certainly a step forward compared to using the app delegate. But, depending on your app, you might also devise a more specific data load controller to encapsulate all the relevant behavior. Indeed, I think the latter option works best even for relatively simple projects.
Problem 2
When I started the application I checked use core data this generated a lot of code for me in the appDelegate. But in this case I can't get to my managedObjectContext on viewController Level, right?
If you look into the appDelegate.h file you should find properties for accessing core data from your controllers:
#property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
#property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
You could use that to access the managed object context through [UIApplication sharedApplication].delegate or you could factor that code out of the app delegate into your own model manager class. Again, this is strictly depending on your app. There are apps that use only 1 managed object contexts, apps that deal with more, etc. If your app does a very basic use of core data, you could leave this as it is.
In my AppDelegate.m, in the application: didFinishLaunchingWithOptions: method, I put the following line:
NSManagedObjectContext *context = [self managedObjectContext];
But it says: AppDelegate may not respond to managedObjectContext. I saw this in a tutorial online, what am I doing wrong? I put #import <CoreData/CoreData.h> in my App_Prefix.pch file (see Adding Core Data to existing iPhone project) but that didn't help.
The goal is to then set myViewController.context = context and then use that context to fetch some data in the view controller.
EDIT: Please see my comment to the answer of O. Begemann.
Create an empty sample app and make sure you check the Core Data checkbox. Then look at the boilerplate code for Core Data that has been generated in the application delegate. You need corresponding pieces of code in your app.
It is likely that the tutorial you are looking at used an iPhone project template that included Core Data. When you create a new project, most templates have a checkbox option to "Use Core Data for storage". Selecting that option creates three methods in your app delegate to retrieve a managedObjectContext, managedObjectModel and persistentStoreCoordinator. You would access those methods using [self managedObjectContext] etc, like in the tutorial you mention.
If you decide to add core data to an existing project and you didn't check off that box mentioned in the tutorial, you'll want to add the properties into the appdelegate header file as well as this important piece in your prefix.pch
#import <Availability.h>
#ifndef __IPHONE_3_0
#warning "This project uses features only available in iOS SDK 3.0 and later."
#endif
#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#endif
Do you have a method with the signature -(NSManagedObjectContext *) managedObjectContext; or a #property(...) NSManagedObjectContext *managedObjectContext; in your AppDelegate.h?
Tim,
Thanks a lot for your suggestion. After creating this boilerplate app I have realized that XCode generates some additional code in the app delegate IF you select CoreData option. I could not understand why Apple's dev guide has relatively long multi-step process for initializing the core data stack and most of the examples just refer to this (non-existing by default!) property. It turns out that all these examples assume that the application was created in certain way.
I want to make a class that can hold settings for my app. Its pretty basic. I will access it from a few different classes, but only want 1 version of this container class to exist so they all see the same data.
Is there something special i need to flag for this?
Here is what I've done so far:
Filters.h
#import <Foundation/Foundation.h>
#interface Filters : NSObject
{
NSString *fuelType;
}
#property (nonatomic, copy) NSString *fuelType;
#end
Filters.m
#import "Filters.h"
#implementation Filters
#synthesize fuelType;
#end
Is there some flag i need to use when i alloc an instance of this class or how should I work this if I need to access the fuelType value from 2 different classes?
Thanks
-Code
For global application settings a better way would be to use NSUserDefaults or if you want to store data for use you should look up using core data and sqlite.
Lastly, if you want to go for ease of use you could do a core data style app delegate class and grab it by using:
[[[UIApplication sharedApplication] delegate] myClass] that way you'll always have that version of the class.
You need a singleton:
you can create your singleton by your own or use AppDelegate object which is an object that is always alive and never released while your application in the frontground (just put the vars needed there and initialize them dynamically).
Here are some links on how to create a singleton.
Cocoa fundamental Guide: Creating a Singleton
and
CocoaDev Singleton Pattern
What you're looking for is a singleton. Most people advise against using singletons though as it is often considered "dirty". See "Singleton" in this apple doc to learn more about it.
In regards to XCode templates with CoreData enabled, I've read unclear use of #property in window app using core data which goes over the 'what' in the templates. But I am having an issue with the 'why'. By declaring the category in the implementation file, the CoreData accessors act like private methods. The problem with that is whenever you want to use CoreData elsewhere in your app, you need some extra code.
I've figured you need to either supply your own method that exposes the managed object context, such as...
- (NSManagedObjectContext *)getManagedObjectContext
{
return self.managedObjectContext;
}
...which will allow other parts of your app to use it.
Or you would need to jam pack your app delegate with specific methods to return managed objects, ie getProducts or setUser.
Can anyone shed light on the reasoning here?
The reason for this is because you should be using dependency injection in your designs. This is the recommended design by the Core Data team. What is expected is that your app delegate will set the NSManagedObjectContext reference in your root view controller(s). From there the controllers will set or inject the necessary dependencies in the following view controllers.
This will lead to a more flexible design. I discussed it in depth in my article on the MDN (http://www.mac-developer-network.com/articles/cd0004.html).
If your project is big and needs to access the managed object context from outside of the AppDelegate,
I would just move the property declaration of managedObjectContext to the header file, as in:
#interface myAppDelegate : NSObject <UIApplicationDelegate> {
NSManagedObjectModel *managedObjectModel;
NSManagedObjectContext *managedObjectContext;
NSPersistentStoreCoordinator *persistentStoreCoordinator;
}
#property (retain,nonatomic) NSManagedObjectContext*managedObjectContext;
#end
Then the other parts of the app can just use appDelegate.managedObjectContext.
There's no reason to expose managedObjectModel or persistentStoreCoordinator outside the app delegate, though.
By the way I have a few comments about your usage of Objective-C:
Don't use get in front of the getter. For a property called foo, the getter should be
-(Foo*)foo;
and the setter should be
-(void)setFoo:(Foo*)_foo;
By convention, get... is used when a pointer is fed as a method argument, as in -[NSString getCharacters:range:] (See the Apple doc).
Follow the proverb, when in Rome, do as the Romans do.
I'm interested in the way I am retaining my properties and the aspects of memory management. This is for a simple application that edits the information of a class that is stored in a table. There are 3 ViewControllers.
A list view (list all classes)
a detail view of the selected item
(lists properties of selected class)
an edit view (lists single property
of selected class and allows it to be
edited)
This is how it is structured at present, what do you think?
ListViewController
#property (nonatomic, retain) NSMutableArray *pools;
#property (nonatomic, retain) PoolFacilityEditController *childController;
To add a new class instance to the table you click an add button that runs this method..
//Loads up the editPoolFacility controller to add a new pool
-(void)add {
PoolFacilityEditController *editController = self.childController;
PoolFacility *aPoolFacility = [[PoolFacility alloc] init];
[self.pools addObject:aPoolFacility];
[aPoolFacility release];
editController.thePoolFacility = aPoolFacility;
editController.pools = self.pools;
[self.navigationController pushViewController:editController animated:YES];
}
The next controller is now loaded up and here are its interesting instance variables. Wise or not I have chose to just assign the pool to the new controller rather than retain. I don't want to unnecessarily retain.
detail View
#property (nonatomic, assign) PoolFacility *thePoolFacility; (assigned in the above add method)
#property (nonatomic, assign) NSMutableArray *pools; (also assigned in the add method)
The detail view has a method that does the following..
- (void)viewWillAppear:(BOOL)animated {
//Pass the copy onto the child controller
if (self.childController.thePoolFacility != self.thePoolFacility) {
self.childController.thePoolFacility = self.thePoolFacility;
}
}
The pool is passed onto the detail edit controller so it knows the pool it is editing.
Now a user clicks on an individual bit of pool information (e.g name) and the detail view controller pops up. It allows the editing of individual properties.
It's interesting properties look like this:
#property (nonatomic, retain) PoolFacility *thePoolFacilityCopy;
#property (nonatomic, assign) PoolFacility *thePoolFacility;
And it creates a copy to edit in case the user changes the values and then wants to cancel. If the user presses save it copies the values from the copy into the non-copy.
- (void)viewWillAppear:(BOOL)animated {
PoolFacility *poolCopy = [self.thePoolFacility copy];
self.thePoolFacilityCopy = poolCopy;
[poolCopy release];
}
If save or cancel is pressed the view is popped.
And then we're back to the middle view that displays all the fields.
Now if the user presses save I just poptheviewcontroller and we're back to the list view. OR if the user presses cancel I run this method.
-(void)cancel {
[self.pools removeObject:self.thePoolFacility];
[self.navigationController popViewControllerAnimated:YES];
}
So to summarize
I am assigning a property throughout different view controllers rather than retaining it.
Also my view controllers are only loaded once and are not deallocated when they 'dissapear'
I hope this made some sense! My question is.. Is this a good way of doing it?
Thanks,
Dan
I didn't see a specific question here, so I'll just make some general critiques.
In iPhone OS, Cancel buttons are common on dialogs meant to add a new item, but much less so on edit dialogs. In fact, the only example of a Cancel button on an Edit dialog I can think of is in the Clock app's Alarm panel. So don't worry about copying the PoolFacility and copying the changes back when it's saved; just make the Cancel button only be visible for new objects (or use the Trash icon--canceling a new pool and deleting an existing one are actually the same action the way things are designed right now).
As you have things now, there's no danger of an object being deallocated at the wrong time. However, if you ever change the storage method--for example, making the app lazily load PoolFacility objects from the disk--it will come back to bite you. Write it properly today and you'll save yourself pain tomorrow. The proper way is to make the thePoolFacility a retained property and release it in your dealloc method. (If you keep managing the pools list the way you currently do, you should do the same thing with it.)
Speaking of which, you don't show how existing PoolFacility objects are loaded. Where do they come from? If there's some kind of database access going on, you may find it helpful to have PoolFacility send notifications when an object is created, updated or deleted, and then observe and react to the appropriate notifications as needed. All of the apps I've written that store user data take this approach; I've found it very handy and flexible.
Since there's only one pool list and it's needed by multiple controllers, there's no shame in storing it in your app delegate instead of passing it around. Better yet, write a FacilityList singleton object that manages the list. This could allow you to take a lot of logic out of your controllers. Generally, you should put everything you can into your models, except the stuff that interacts with the screen. That means that when Apple makes the iTablet or releases the Apple TV SDK--or just when you decide to make a Mac version or redo the user interface--you can bring as much of your app as possible over unmodified.