I have found these Stanford tutorials https://itunes.apple.com/us/course/ipad-iphone-app-development/id495052415, and have been listening a lecture about core data, which is really great.
It shows how to access Core Data via UIManagedDocument... Only thing I don't understand how UIManagedDocument knows which model should it use, because I don't see it set anywhere?
To sum it when using UIManagedDocument, how do you define database model that is gonna be used?
I found the answer:
'UIManagedDocument' takes all the models from your application's main bundle, and makes union of these models. If you only have one model that model is used.
This can be changed by overriding 'UIManagedDocument' class.
Loading a Data Model
In some cases, you do not have to write any code to load a model. If you use a document-based application on OS X, NSPersistentDocument manages the task of finding and loading your application’s model for you. If you use Xcode to create a non-document application that uses Core Data (for OS X or for iOS), the application delegate includes code to retrieve the model. The name of a model—as represented by the filename used to store it on disk—is not relevant at runtime. Once the model is loaded by Core Data, the filename is meaningless and has no use, so you can name the model file whatever you like.
If you want to load a model yourself, there are two mechanisms you can use:
You can load a single model from a specific URL, using the instance method initWithContentsOfURL:.
This is the generally-preferred technique. Typically an application has a single model, and using this method you ensure that you load only that model. You can also load individual models via URLs and then unify them using modelByMergingModels: before instantiating a coordinator with them.
In cases where you have more than one model—and particularly in cases where the models represent different versions of the same schema—knowing which model to load is essential (merging together models with the same entities at runtime into a single collection would cause naming collisions and errors). This method is also useful if you want to store the model outside of the bundle for your application, and so need to reference it via a file-system URL.
You can create a merged model from a specific collection of bundles, using the class method mergedModelFromBundles:.
This method may be useful in cases where segregation of models is not important—for example, you may know your application and a framework it links to both have models you need or want to load. The class method allows you to easily load all of the models at once without having to consider what the names are, or put in specialized initialization code to ensure all of your models are found.
Accessing and Using a Managed Object Model at Runtime
NSManagedObjectModel *model = <#Get a model#>;
NSFetchRequest *requestTemplate = [[NSFetchRequest alloc] init];
NSEntityDescription *publicationEntity =
[[model entitiesByName] objectForKey:#"Publication"];
[requestTemplate setEntity:publicationEntity];
NSPredicate *predicateTemplate = [NSPredicate predicateWithFormat:
#"(mainAuthor.firstName like[cd] $FIRST_NAME) AND \
(mainAuthor.lastName like[cd] $LAST_NAME) AND \
(publicationDate > $DATE)"];
[requestTemplate setPredicate:predicateTemplate];
[model setFetchRequestTemplate:requestTemplate
forName:#"PublicationsForAuthorSinceDate"];
Using a fetch request template
NSManagedObjectModel *model = <#Get a model#>;
NSError *error = nil;
NSDictionary *substitutionDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
#"Fiona", #"FIRST_NAME", #"Verde", #"LAST_NAME",
[NSDate dateWithTimeIntervalSinceNow:-31356000], #"DATE", nil];
NSFetchRequest *fetchRequest =
[model fetchRequestFromTemplateWithName:#"PublicationsForAuthorSinceDate"
substitutionVariables:substitutionDictionary];
NSArray *results =
[aManagedObjectContext executeFetchRequest:fetchRequest error:&error];
Related
I'm just learning core data and mostly coping, but having a little bit of trouble thinking about how I'll implement it.
I want to access the same Core Data from throughout my app. What would be the best way to access it from multiple classes? Would it be recommended to have it as a global variable (normally not recommended in object oriented programming), or to pass it as an instance variable between classes?
Core data model will be available throughout your app. You can easily access the managed object through out your app. You just need to make an instance of the AppDelegate class.
Say for example you have stored contacts. You can just use [managedObject valueForKey:#"name"] in any of the view controllers.
1. Create an instance of the appDelegate
self.theappDel=[[UIApplication sharedApplication] delegate];
2. Get the context,fetch request and entity description.
NSManagedObjectContext*context=[self.theappDel managedObjectContext];
NSEntityDescription*entity=[NSEntityDescription entityForName:#"Contacts" inManagedObjectContext:context];
NSFetchRequest*request=[[NSFetchRequest alloc] init];
[request setEntity:entity];
NSManagedObject*managedObject=nil;
NSError*error=nil;
NSArray*objectList=[context executeFetchRequest:request error:&error];
3. Get the managed object from the array.
if([objectList count]>0)
managedObject=[objectList objectAtIndex:0];
NSLog(#"The name: %#",[managedObject valueForKey:#"name"])
4. Pass the name object using a singleton(or any convenient method) pattern, in other view controllers that you need it.
Pass the NSManagedObjectContext instance, or if you just need to handle one object the NSManagedObject instance, to the next class.
Like it's done in Xcodes Core Data templates.
Take a look at the MagicalRecord Library. Not only does it make a lot of common fetch requests much more succinct, it also makes it easier to access your managed object context just by using [NSManagedObjectContext defaultContext];
Totally new to Objective-C and Core Data, coming from a .net background I really want to put all of my fetch requests into some sort of class that I can call, preferably statically to get my objects, something like:
ObjectType *myObject = [CoreDataDAL GetObject:ID];
Anyone have a pattern to implement this?
I am hacking my way through one right now but it's probably not quite right, will post code when I have it.
EIDT:
Here is my code as it stands right now - seems to work great - please rip it part if I am going down the wrong road - here is the basic DAL:
#import "CoreDataDAL.h"
#import "CoreDataAppDelegate.h"
#implementation CoreDataDAL
#synthesize managedObjectContext;
-(id)init {
if (self=[super init]) {
CoreDataAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
self.managedObjectContext = appDelegate.managedObjectContext;
}
return self;
}
-(Client *) GetClient:(NSString *) ClientID{
/* Client Fetch Request */
NSFetchRequest *request = [[NSFetchRequest alloc]init];
NSEntityDescription *entityType = [NSEntityDescription entityForName:#"Client" inManagedObjectContext:managedObjectContext];
[request setEntity:entityType];
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"ClientID==%#",ClientID];
[request setPredicate:predicate];
NSError *error;
NSArray *entities = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
return [entities objectAtIndex:0];
}
#end
And here is how it is used in my view controllers:
CoreDataDAL *dal = [[CoreDataDAL alloc]init];
Client *client = [dal GetClient:clientID];
[dal release];
Seems straight forward enough, thoughts?
Don't do this; what you're doing is porting a pattern from one context to another where it doesn't really make sense.
For one thing, you shouldn't be modeling IDs at all in Core Data; the framework does that for you with NSManagedObjectID already. Thus a -clientWithID: method on a CoreDataDAL class is redundant. (Note that I've also changed the name of your hypothetical method to follow proper Cocoa naming conventions.) Instead, you can just use -[NSManagedObjectContext objectWithID:] or -[NSManagedObjectContext existingObjectWithID:error:] to get an object based on its NSManagedObjectID.
Similarly, relationship management is handled for you. You don't need to have a method in your DAL that can (say) fetch all of the Address instances that apply for a given Client by evaluating some query. You can just traverse your Client's to-many addresses relationship to get at them, and manipulate the same relationship directly (rather than setting foreign keys etc.).
Finally, if you really do want to have methods to perform specialized queries, you can either specify the query via a fetched property on the appropriate entity for its results, or you can add that method directly to the appropriate class. Class methods in Objective-C aren't like static methods in C++, Java or C# - they can be overridden just as instance methods can, and are much more appropriate for this kind of use.
For example, say your Client entity has a syncID property representing the ID of the object that it represents in some web service. (Note that this is specifically for relating a local object to a remote object, not the "primary key" of the local object.) You'd probably have class methods on the MyClient class associated with your Client entity like this:
#implementation MyClient
+ (NSString *)entityClassName
{
return #"Client";
}
+ (NSEntityDescription *)entityInManagedObjectContext:(NSManagedObjectContext *)context
{
return [NSEntityDescription entityForName:[self entityClassName] inManagedObjectContext:context];
}
+ (MyClient *)clientWithSyncID:(NSString *)syncID
inManagedObjectContext:(NSManagedObjectContext *)context
error:(NSError **)error
{
MyClient *result = nil;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[self entityInManagedObjectContext:context]];
[request setPredicate:[NSPredicate predicateWithFormat:#"syncID == %#", syncID]];
[request setFetchLimit:1];
NSArray *results = [context executeFetchRequest:request error:error];
if ([results count] > 0) {
result = [results objectAtIndex:0];
} else {
if (error != NULL) {
*error = [NSError errorWithDomain:MyAppErrorDomain
code:MyAppNoClientFoundError
userInfo:nil];
}
}
return result;
}
#end
This is similar to what you wrote in your DAL class, but instead of consolidating all of the fetches in one place, it puts the logic for fetches appropriate to a particular managed object class on that class, which is really where it belongs. Thanks to the fact Objective-C has true class methods, you can actually put methods like +entityInManagedObjectContext: and +entityClassName on a common base class and then override only the latter as appropriate in subclasses (or even have it generate an appropriate entity name from the class name).
To sum up:
Don't recreate what Core Data already implements for you in terms of things like object IDs, relationship management, and so on.
Leverage polymorphism at both the instance and the class level to keep your code clean, rather than use "utility" classes like "data access layers."
Fetch request properly belong to the individual controllers in the Model-View-Controller pattern. A fetch returns the specific information, in the specific order, required by each individual view. Each fetch is customized for the needs of each particular view. As such, putting all of an app's fetches in a single object would break encapsulation instead of enhancing it. Only fetched properties and fetched relationships belong in the data model itself.
The managed object context performs the function of the data object in simple apps. It implements all the functions necessary to get information in and out of the Core Data stack e.g. - objectWithID:. Much of the time, all you need to do is pass the context to controllers and let them configure fetches to it.
If your app is large or has multiple context, you can always wrap up the context/s in a custom manager class with various accessors and convenience methods to make the Core Data operations run more smoothly. You can legitimately and safely implement the manager class as a singleton to make accessing it everywhere in the app easy.
Edit:
The code looks okay except for the mutable copy. It's pointless and will leak memory. The entities array is only needed for one line and it's autorelease. The Client objects retention is managed by the context. You should test the error and at least log it for debugging.
You do want to avoid "get" and "set" for method names that are not accessors. The runtime looks methods that begin with "get" and "set" to find accessors. By convention, all method names start with lower case. You might want to make the method name more descriptive so that it auto-comments when you read it months down the road.
So:
[theCoreDataDal GetClient:vaugelyNamedString];
to
[theCoreDataDal clientWithClientID:vaugelyNamedString];
The problem your going to run into with trying to cram everything into one object is that the fetches are usually unique and configured for the needs of a specific interface.
Moreover, you usually start with a fetch to find specific objects but then you spend the rest of the time walking relationships based on input unknown until runtime.
Core Data is the data access layer. Most of the code you write for Core Data is actually controller code. There is nothing conceptually problematic about this GetClient method but how often are you going to execute this particular fetch?
When I create a Data Model Manager object, I use it largely to store boiler plate code. For example, while each fetch request is unique, they all start out the same with an entity description so I autogenerate methods to return the basic fetch for each entity and put that in the manager. Then I have another boiler plate method to actually perform the fetch. In use, a controller ask the manager for a fetch object for a specific entity. The controller customizes the fetch and then sends it back to the manager to perform the fetch and return the results.
Everything boiler plate is in the manager and everything customized is in the controller.
I think I understand the error message: CoreData could not fulfill a fault, but I am not sure how I should deal with it.
We have an application where we use Core Data to persist data returned from a JSON service. Today I am doing the following.
Fetch local object from persistent store and return to UI
Ask server if the object is updated - when I get the answer, I update the Core Data managed object
Update UI with the updated object
The problem is; even if I do not use multi threads I sometimes gets an error when the HTTP request deletes managed objects that my UI has retained. I tried to fetch the objects with returnsObjectsAsFaults to NO. I thought I then could access all the relations and properties of an managed object even if it was deleted (as long as my UI had retained it).
How should I solve this issue?
I thought I could use separate NSManagedObjectContext for read and write. I have made this test:
MyAuthorMO *authorUpdate = [[MyAuthorMO alloc] init]; // I have made this init insert the object into the updateContext
authorUpdate.firstname = #"Hans";
authorUpdate.lastname = #"Wittenberg";
authorUpdate.email = #"Hans#somedomain.no";
NSManagedObjectContext *updateContext = [[MyCoreManager getInstance] managedObjectContext];
NSError *error = nil;
[updateContext save:&error];
NSManagedObjectContext *readContext = [[MyCoreManager getInstance] readOnlyContext];
NSFetchRequest *fetchRequest = [managedObjectModel fetchRequestFromTemplateWithName:#"authorByEmail" substitutionVariables:[NSDictionary dictionaryWithObject:#"Hans#somedomain.no" forKey:#"EMAIL"]];
[fetchRequest setReturnsObjectsAsFaults:NO];
NSArray *authors = [readContext executeFetchRequest:fetchRequest error:&error];
MyAuthorMO * readAuthor = [authors objectAtIndex:0];
// Delete the author with update context:
[updateContext deleteObject:authorUpdate];
[updateContext save:&error];
NSLog(#"Author: %# %#, (%#)", readAuthor.firstname, readAuthor.lastname, readAuthor.email);
The log is outputted just fine as long as I use the readContext for the fetch. If I use the updateContext for the fetch, I get an exception. This looks promising, but I am afraid that I will run into problems at a later stage. Sooner or later I will probably try to access a property that is not fetched completely (a fault). How can I achieve the behaviour I am looking for?
You shouldn't retain managed objects that the context has released. Let the context do that for you.
The problem is that managed objects can exist as either faults or actualized objects. When you retain one, you may retain the fault which contains no data. Even if you do retain the actual object, the object may not behave properly once it has been separated from its context.
In order to handle your scenario, you need a context for the UI and then a context for the server. After either context makes changes, you should merge the context to ensure both are properly updated relative to the store.
Your UI should be configured to reflect the state of data model, you shouldn't have parts of the data model dependent on the state of the UI.
I had the same problem in my database because I refer to object which didnt exist (because I remove it with other relationed object). My solution was to set "No Action" in my relationship.
This is for an iPhone App, but I don't think that really matters. I need to send a custom object (which is managed by Core Data) over bluetooth using the iPhone's GameKit. Normally, I would just use an NSKeyedArchiver to package up the object as a data object and ship it over the line, then unarchive the object and I'm done. Of course, I would need to implement the initWithCoder: and encodeWithCoder: methods in my custom object as well.
I'm not sure if this can be done with an NSManagedObject class, which is managed by Core Data or not. Will they play nice together? I'm guessing once I ship the encoded managed object over to the other device and unencode it, I would just add this received object to the other device's context. Is this correct? Am I missing any steps?
An NSManagedObject instance can't meaningfully exist outside of an NSManagedObjectContext instance, so I wouldn't bother trying to do the NSCoding dances required to directly serialize and deserialize an NSManagedObject between two contexts (you can do this; see below). Instead I would create a dictionary with the appropriate attribute key/values (you can get the attribute names via the managed object instance's attribute names via instance.entity.attributesByName.allKeys (you can use [instance dictionaryWithValuesForKeys:keys] to get the dictionary of attribute:value pairs) . I would send relationship information as NSURL-encoded NSManagedObjectIDs. Don't forget to include the instance managedObjectID (as an NSURL) in the dictionary so that you can reconnect any relationships to the object on the other end. You'll have to recursively create these dictionaries for any targets of relationships for the instance you're encoding.
Then send the dict(s) across the wire and reconstitute them on the other end as instances in a new managed object context (you can use setValuesForKeysWithDictionary:).
You may notice that this is exactly what the NSCoder system would do for you, except you would have to use the classForCoder, replacementObjectForCoder: and awakeAfterUsingCoder: along with a custom NSDictionary subclass to handle all the NSManageObject-to-NSDictionary mapping and visa versa. This code is more trouble than it's worth, in my experience, unless you have a complex/deep object graph that you're trying to serialize. For a single NSManagedObject instance with no relationships, it's definitely easier to just do the conversion to a dict and back yourself.
This sounds like a job for TPAutoArchiver.
I suggest the dictionary solution for simpler options. However, here is how I solved the issue. My model was already sizable and robust, with custom classes and a single root class above NSManagedObject.
All that I needed was for that single class to call the appropriate designated initializer of NSManagedObject: [super initWithEntity:insertIntoManagedObjectContext:]. This method, and the metadata in an NSEntityDescription is what sets up the implementations of all the dynamic accessors.
- (id)initWithCoder:(NSCoder *)aDecoder {
CoreDataStack *cds = [LibraryDiscoverer unarchivingCoreDataStack];
NSEntityDescription *entity = [cds entityDescriptionForName:[[self class] entityName]];
NSManagedObjectContext *moc = [cds managedObjectContext];
self = [super initWithEntity:entity insertIntoManagedObjectContext:moc];
self.lastEditDate = [aDecoder decodeObjectForKey:#"lastEditDate"];
return self;
}
The CoreDataStack is my abstraction around CoreData. The LibraryDiscoverer is a global access hook to get hold of the core data information. The entityName is a method defined to provide the entity name from the class name; if you follow a naming convention (i.e. class name = entity name) it can be implemented generically.
All the other initWithCoder: methods in my class hierarchy are standard NSCoder, with the note that you don't need to encode both directions of a relationship, CoreData reconnects that for you. (As it always does, including with the dictionary solution.)
I'm slightly confused in one aspect of Core Data. That is, when do I use the rudimentary alloc/init routine vs created an object with core data and saving it into the current managed object context.
I know that's a rather vague question, so let me give you an example.
I have an application I'm currently working on that iterates through all of a user's contact book on the iPhone. From there, I wrote a model class called 'Person'. I used to do something like this in a loop of people:
Person *person = [[Person alloc] initWithWrapper:mywrapper];
mywrapper would contain an NSDictionary with the attributes for person. Later I'd be able to populate the address book in my app with the person objects.
Now I've started rebuilding parts of the app with Core Data. Do I continue using the strategy above to populate my address book? Or do I do something like this instead:
Person *person = (Person *)[NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:managedObjectContext];
[person setName:name];
[person setDob:dob];
// Commit the change.
NSError *error;
if (![managedObjectContext save:&error]) {
// Handle the error.
}
The problem is, this code gets executed everytime the app gets started. Should I not be using core data as it will populate the storage mechanism with redundant instances of person everytime the app loads? Should I modify my NSManagedObject (Person class) and add my initWithWrapper: method and continue as I normally would there?
Slightly confused, would love clarification.
You should never be initializing Core Data objects outside of a managed object context - there's simply no point. Having some
Person *person = [[[Person alloc] init] autorelease];
does you no good since you can't save the object, manipulate it, or really do anything useful that Core Data provides without the context (and thus model and store coordinator) backing it up.
You should instead only use the alloc-init combo when you are inserting an object into Core Data for the first time; this is what the initWithEntity:insertIntoManagedObjectContext: method is for. And you're right, every time you call that method you are inserting a new object into the Core Data context and therefore store, and you may wind up with duplicate objects if you're not careful.
What I would instead recommend for you, if you're running code on every startup, is to come up with a Core Data query that returns some set of existing Person objects, and only add objects (using the initialization method) that don't already exist in the store. If the object already exists, modify it instead of creating a new one.
The trick is getting something like this to perform properly. You shouldn't do a Core Data fetch for every contact in the iPhone address book; many small fetches like this are very expensive. You could in theory get two NSSets - one of Person objects, and one of contacts - then compare them by some unique key (like a hash of the first and last names of the contact). I leave the optimization to you.
The key point is this: don't use alloc and init on a Core Data object unless you mean to insert that object for the first time into a context. Instead look at your existing objects and modify them if necessary.
Yeah, it's simplest to add the initWithWrapper method to your Person class. It would be something like this:
- (id) initWithWrapper:(NSDictionary *)wrapper {
NSEntityDescription * person = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:someMOC];
if (self = [super initWithEntity:person insertIntoManagedObjectContext:someMOC]) {
//do your wrapperly initialization here
}
return self;
}
The only downside to this is that this method has to know which managedObjectContext it should insert the object into, so you have to figure out a way to provide that.
That being said, I use this pattern myself all the time.