I have the following relationship defined in Core Data
Person --> Worked <-- Job
I have a view that shows Person information in a tableview. The user can then click on Worked Items to see all worked items for that person (Worked entity shows hours worked and related job).
I then push a view showing worked jobs for that person.
I also show, in a picker view, a list of jobs that can be added to the Worked list.
I've tried to do this every which way, but I'm not sure if I'm going about the right way, so I'd like the experts' input on this.
What should I pass into the Worked view? I currently pass in the Person object containing the Worked NSSet to load the table view. Then I use a NSFetchedResultsController to load the picker.
So I got the add functionality working, by using the Person and Job addWorkedObject: methods.
But I need to let the user delete a worked item from the table view.
Should I be using two NSFetchedResults? If so, how?
I'm really at my witt's end with this one, so if anyone can help, I'd really appreciate it.
Thanks,
Rod
I'd need a little more information. First, are you relationships uni-directional as your diagram suggests? From what I've read, Core Data is much happier with bidirectional relationships.
For example, this model (below)
Person <-->> Worked <<--> Job
states that a Person can 'have' many Worked objects, while a Worked object will be associated with at most one Person. Similarly, a Job could be associated with many Worked objects but a Worked object would only ever be associated with at most one Job. Is this your model?
If so, I would use an NSFetchedResultsController for the root view (the one that is showing all Persons). But I would just use NSSets/NSArrays to populate UITableView and UIPickerView.
This is a method that I use for debugging - but it might provide a starting point for getting all Jobs, for example. For your purposes, you could populate an ivar NSArray with the NSManagedObjecs in 'items'.
- (void) dumpAllObjects:(NSString *) entityDescription
{
DLog(#"Dump all object of type '%#']", entityDescription);
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:entityDescription
inManagedObjectContext:managedObjectContext]];
NSError *error = nil;
NSArray *items = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
DLog(#" There are %d instances of entity '%#'", [items count], entityDescription);
for (NSManagedObject *managedObject in items)
{
if ([managedObject respondsToSelector:#selector(dump)])
[managedObject dump];
}
}
Meanwhile, I would subclass UIViewController for your view that shows Worked objects and Job objects - and have that class implement UITableViewDelegate, UITableViewDataSource, UIPickerViewDelegate and UIPickerViewDataSource.
As for deleting, I am not sure which aspect is causing problems. If you have -addWorkedObject: methods for Person and Job, then you should also have -reomveWorkedObject: methods. No?
Hope this helps.
Related
So I got entities Level and Tile. Level has a to-many relationship with Tile. Tile has a property 'index'.
Right now I'm using this code to get the tiles array of Level sorted:
- (NSArray *)sortedTiles
{
NSMutableArray *sortedTiles = [NSMutableArray arrayWithArray:[self.tiles allObjects]];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"index" ascending:YES];
[sortedTiles sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[sortDescriptor release];
return sortedTiles;
}
This works, but I want to be able to retrieve a single Tile with a certain index, so I wrote this method in Level.h:
- (Tile *)tileWithIndex:(NSInteger)index;
The implementation is fairly simple:
- (Tile *)tileWithIndex:(NSInteger)index
{
NSArray *sortedTiles = [self sortedTiles];
Tile *tile = [sortedTiles objectAtIndex:index];
return tile;
}
Now, ofcourse this isn't the most efficient way in doing so because the tiles array has to be allocated and sorted each time, so I was thinking: if I just add an instance variable to Level, 'sortedTiles', then I won't have to rebuild it each time. But Level is a subclass of NSManagedObject, so is this possible and/or wise to do?
I wouldn't. The NSManagedObjects are a reflection of a record in the database and adding additional member variables outside the model strikes me as adding something that doesn't belong.
There are other, better ways to accomplish the same thing. The simplest would be to get the controller or delegate that's fetching all these objects to fetch, sort and retain the array locally.
For large or complex situations, you can use an NSFetchRequestController to collect, sort and dole out NSManagedObjects as needed. This integrates nicely into a UITableViewController. I haven't done any performance testing, but for a situation where there are potentially a large number of records, I would try this first to see if the Fetch Results classes own cache management is sufficient.
My app involves a main screen with several sorting/viewing options of a set of data. Depending on what the user chooses, I may list them, e.g. alphabetically, N most recent, or grouped somehow.
I started the app as a Core Data Table-based navigation app; my app delegate sets up the Core Data stack (unchanged generated code), gives the NSManagedObjectContext to the controller for the initial screen, and it passes it to the UITableViewController implementing my "list of entities".
Since my three different views of the same data all end up showing a table listing out the data, I expanded this class to have three different NSFetchedResultsControllers, each with the one UITableViewController instance as their delegate. Before pushing this view controller on the stack, I call a method to switch which NSFetchedResultsController to use, e.g.
-(void)configureForMostRecent {
self.activeFetchedResultsController = self.mostRecentResultsController;
}
Now I am getting random crashes from Core Data, e.g. NSInternalInconsistencyException and other things like that. Sometimes, I use the app and everything's fine, other times, it crashes almost instantly.
So, my instinct is that my design is just a Bad Idea(tm).
Should I basically stick to a "One UITableViewController to one NSFetchedResultsController" sort of model and just use other coding styles to reduce boilerplate?
Using multiple NSFetchedResultsController instances is a perfectly valid design based on the description you have given so far.
Are you trying to use the same cache for each of these NSFetchedResultsController instances? Are you calling -reloadData on the table whenever you switch to a different NSFetchedResultsController? Both of those could be causing the crash you are seeing.
Update
The delegate is not an issue but not calling -reloadData is going to be a killer. The delegate methods really are there just to update the UITableView when the NSFetchedResultsController changes. The fact that a reference to is passed into those delegate methods is a hint that they are designed to handle multiple NSFetchedResultController` instances calling into them.
You could use one fetch controller, adjusting the fetch predicate and refetching as needed.
EDIT
Following my example case:
[NSFetchedResultsController deleteCacheWithName:#"MyObjectsCache"];
NSPredicate *_predicate = nil;
if (condition) {
_predicate = [NSPredicate predicateWithFormat:mySearchPredicateString];
self.currentTableView = searchDisplayController.searchResultsTableView;
}
else {
_predicate = [NSPredicate predicateWithFormat:myDefaultPredicateString];
self.currentTableView = tableView;
}
[fetchedResultsController.fetchRequest setPredicate:_predicate];
NSError *_error = nil;
if (![fetchedResultsController performFetch:&_error]) {
// handle error
}
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.
From an Apple example, I have this:
Event *event = (Event*)[NSEntityDescription
insertNewObjectForEntityForName:#"Event"
inManagedObjectContext:self.managedObjectContext];
Event inherits from NSManagedObject. Is there a way to avoid this weird call to NSEntityDescription and instead just alloc+init somehow directly the Event class? Would I have to write my own initializer that just does that stuff above? Or is NSManagedObject already intelligent enough to do that?
NSManagedObject provides a method called initWithEntity:insertIntoManagedObjectContext:. You can use this to do a more traditional alloc/init pair. Keep in mind that the object this returns is not autoreleased.
I've run into the exact same problem. It turns out you can completely create an entity and not add it to the store at first, then make some checks on it and if everything is good insert it into the store. I use it during an XML parsing session where I only want to insert entities once they have been properly and entirely parsed.
First you need to create the entity:
// This line creates the proper description using the managed context and entity name.
// Note that it uses the managed object context
NSEntityDescription *ent = [NSEntityDescription entityForName:#"Location" inManagedObjectContext:[self managedContext]];
// This line initialized the entity but does not insert it into the managed object context.
currentEntity = [[Location alloc] initWithEntity:ent insertIntoManagedObjectContext:nil];
Then once you are happy with the processing you can simply insert your entity into the store:
[self managedContext] insertObject:currentEntity
Note that in those examples the currentEntity object has been defined in a header file as follows:
id currentEntity
To get it to work properly, there is a LOT of stuff to do. -insertNewObject:... is by far the easiest way, weird or not. The documentation says:
A managed object differs from other
objects in three main ways—a managed
object ... Exists in an environment
defined by its managed object context
... there is therefore a lot of work
to do to create a new managed object
and properly integrate it into the
Core Data infrastructure ... you are
discouraged from overriding
initWithEntity:insertIntoManagedObjectContext:
That said, you can still do it (read further down the page to which I linked) but your goal appears to be "easier" or "less weird". I'd say the method you feel is weird is actually the simplest, most normal way.
I found a definitive answer from More iPhone 3 Development by Dave Mark and Jeff LeMarche.
If it really bothers you that you use a method on NSEntityDescrpiton rather than on NSManagedObjectContext to insert a new object into an NSManagedObjectContext, you can use a category to add an instance method to NSManagedObjectContext.
Create two new text files called NSManagedObject-Insert.h and NSManagedObject-Insert.m.
In NSManagedObject-Insert.h, place the following code:
import <Cocoa/Cocoa.h>
#interface NSManagedObjectContext (insert)
- (NSManagedObject *)insertNewEntityWithName:(NSString *)name;
#end
In NSManagedObject-Insert.m, place this code:
#import "NSManagedObjectContext-insert.h"
#implementation NSManagedObjectContext (insert)
- (NSManagedObject *)insertNewEntityWithName:(NSString *)name
{
return [NSEntityDescription insertNewObjectForEntityForName:name inManagedObjectContext:self];
}
#end
You can import NSManagedObject-Insert.h anywhere you wish to use this new method. Then replace the insert calls against NSEntityDescription, like this one:
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
with the shorter and more intuitive one:
[context insertNewEntityWithName:[entity name]];
Aren't categories grand?
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.