Can I put NSFetchedResultsController inside a Managed Object Class? - objective-c

I'm sick and tired of constantly having to putting/repeat the NSFetchedResultsController code for my project in virtually every file where I'm working with the Managed Object Context.
I want to reduce the amount of repetitive code. I want to put this kind of repetitive CRUD-like code inside the model class.
What I want instead is put all my custom NSFetchs inside the Managed Object Class for the relevant Entity (ie: Company.m, Employee.m).
Even the Core Books sample code from Apple does not put this code into the Managed Object Class and I'm wondering if its possible?
I tried pasting the code into my Company.m class but it keeps complaining about the managedObjectContext and also it complains that fetchedResultsController is not declared, even though its a parameter?
Ideally, I would like to put lots of different kinds of fetch request/results controller stuff inside the Entity Managed Object Class too.
But before I get ahead of myself, Is it possible, therefore, just to put all the NSFetchedResultsController stuff inside the Entity Managed Object class?
If there is a sample tutorial or project or source code that covers this, that'd be great too.
Thanks.
(code sample follows).
/**
Returns the fetched results controller. Creates and configures the controller if necessary.
*/
- (NSFetchedResultsController *)fetchedResultsController
{
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
// Create and configure a fetch request with the Book entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Company" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
// Create the sort descriptors array.
NSSortDescriptor *authorDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:authorDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Create and initialize the fetch results controller.
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:#"author" cacheName:#"Root"];
self.fetchedResultsController = aFetchedResultsController;
fetchedResultsController.delegate = self;
// Memory management.
[aFetchedResultsController release];
[fetchRequest release];
[authorDescriptor release];
[sortDescriptors release];
return fetchedResultsController;
}

I recommend using ActiveRecord with xmod. CoreData will overwrite your CRUD if you modify your core data model. ActiveRecord makes it as easy as calling [MyManagedObject createEntity]; and NSArray *myObjects = [MyManagedObject findAll]; There is also options to pass predicates to filter the findAll call. The xmod addition generates a subclass to the generated classes so that you can add custom logic to your entities so that they do not get overridden.
Edit: I would like to add the link to this Active Record implementation since this is the one I actually use.
Edit2: This has now been renamed to Magical Record.

Related

Is mutableCopy call returns an array with a copy of the managed object context?

In a tutorial i'm learning CoreData from the preform something like this to fetch the collection of notes in a notes app:
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Note"];
self.notes = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
[self.tableView reloadData];
}
So first of all, notes is an NSMutableArray, so tell me if I understand it right:
they creating an NSManagedObjectContext object to hold the context.
they create a request to get the "Note" entity from the database file.
they use the managedObjectContext to call executeFetchRequest with the requested request (which is fetchRequest). Now here is the part I dont completely understand (probably some of the previous ones as well, please correct me if I didn't):
The type of object i'm getting from this call [managedObjectContext executeFetchRequest:fetchRequest error:nil]; is an NSSet? and by calling mutableCopy i'm returning an array?
Thanks
[managedObjectContext executeFetchRequest:fetchRequest error:nil]
returns an (immutable) NSArray, and mutableCopy creates a - well - mutable copy
of that array. It does not copy the managed objects in the array or the context.
It just allows you to modify self.notes, e.g. to add, delete or rearrange the objects
in the mutable array.
Remark: If you display objects from a Core Data fetch request in a table view
then you should have a look at NSFetchedResultsController. It might look a bit more
complicated at the beginning, but allows (for example) automatic updates of the
table view if objects are inserted, deleted or modified.

Core data debugger hangs at assigning value to entity

I have a core data entity to which I am trying to assign relationship from another entity. Please refer the code below
#define kId #"id"
-(NSArray *)fetchObjectsForEntityName:(NSString *)entityName withPredicate:(NSPredicate *)predicate
{
NSManagedObjectContext *newContext = [Helper generateNewContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:newContext];
NSFetchRequest *request = [NSFetchRequest new];
[request setEntity:entity];
if (predicate)
[request setPredicate:predicate];
NSError *error = nil;
NSArray *resultArray = [newContext executeFetchRequest:request error:&error];
return resultArray;
}
-(void)updateCoreDataEntity
{
NSArray *objectsArray = [self fetchObjectsForEntityName:#"FirstEntity" withPredicate:nil];
//FirstObjects is a subclass of NSManagedObject class (Custom entity)
//kId is just #define as defined above
//Recasting removed
for (FirstObjects *firstObject in objectsArray) {
if ([firstObject.id isEqualToString:[dict valueForKey:kId]]) {
secondEntity.firstEntity = firstObject; //debugger hangs here
}
}
}
I am trying to fetch objects that belong to "FirstEntity" into an NSArray
Loop through that array to find the required object.
Then assign the "firstObject" to SecondEntity if the criteria matches.
However, I am getting nowhere with this code as the debugger (and the code) hangs at the last line of code.
What is the mistake I am doing, can anyone help with this code.
Regards,
iSee
secondEntity is maybe undefined. This would surely lead to a crash.
Also, the logic of the ID is flawed. It seems the comparison is not comparing to a specific ID but to the generic string "id". Perhaps you really want to compare to a dynamically allocated id? Also, are these string ids unique? (If not you might get unpredictable results.)
Finally, from the code it is not clear if Helper provides always the same managed object context. This would be strongly advised - separate contexts are mainly used for concurrency.

CoreData: error: Failed to call designated initializer on NSManagedObject class

I have a little damn problem with CoreData. I want to insert a new Object, so I first have to create one. This is done by that code:
Challenges *newChallenge = [[Challenges alloc] init];
[newChallenge setName:#"TestChallenge"];
[newChallenge setRounds:[[NSNumber alloc] initWithInt:12]];
[newChallenge setShots:[[NSNumber alloc] initWithInt:5]];
[newChallenge setDate:[NSDate date]];
But however after the alloc init I get this error:
CoreData: error: Failed to call designated initializer on NSManagedObject class 'Challenges'
What the hack is going wrong?
I think the problem is that Challenges is a NSManagedObject class and you need the designated initializer:
initWithEntity:insertIntoManagedObjectContext:
instead of
Challenges *newChallenge = [[Challenges alloc] init];
Read More..
In case you ask yourself "OK, so how to I get that entity?" (like I did), you do this by using the entityForName method like so:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Challenges" inManagedObjectContext:self.managedObjectContext];
Challenges *newChallenge = [[Challenge alloc] initWithEntity:entity insertIntoManagedObjectContext:self.managedObjectContext];
Hope this helps, this thread has helped me a lot!
NSManagedObject cannot be just alloc/init like you would normally do with an NSObject. As a matter of fact the designated initializer is:
initWithEntity:insertIntoManagedObjectContext:
Now, for the actual error, Apple states in the documentation that:
Important: This method is the designated initializer for
NSManagedObject. You must not initialize a managed object simply by
sending it init.
So, you could see that you need 2 things in order to initialize it, an NSEntityDescription (which entity you intend to instantiate) and an NSManagedObjectContext (the context that the new object will be created into).
Others have already stated why its not working. Here is how you can cut down on the boilerplate and make your code more readable:
#implementation NSManagedObject(MyPrivateAdditions)
+ (id)insertNewObjectInContext:(NSManagedObjectContext *)context
{
return [NSEntityDescription insertNewObjectForEntityForName:self.className inManagedObjectContext:context];
}
#end
now you can do:
Challenges *newChallenge = [Challenge insertNewObjectInContext:context];
Additionaly, if your Challenges class is NSManagedObject and date, rounds and shots are defined as its attributes you can add method:
-(void) awakeFromInsert {
self.date = [NSDate date];
self.rounds = #(12);
self.shots = #(5);
}
Each new object will have defined those attributes from its birth.

Design pattern to deal with similar methods

I have two methods in my objective-c class that do similar but different things. (they both retrieve a set of core-data records containing JSON, unpack and examine the JSON documents and do some stuff depending upon the structure of the JSON).
The first method looks like this:
+(NSDictionary*)getListOfResponsesWithForm:(NSString*)formId
{
NSError* requestError = nil;
// NSIndexSets don't allow easy targetted access into the set, so use arrays instead.
NSMutableArray* indexSetOfAllEntries = [[NSMutableArray alloc] init];
NSMutableArray* indexSetOfEntriesForLoggedOnUser = [[NSMutableArray alloc] init];
NSString* activeUserEmail = getActiveUser().email;
NSFetchRequest* fetchRequest = [ [NSFetchRequest alloc] init];
NSEntityDescription* entityDesc = [NSEntityDescription entityForName:#"Response" inManagedObjectContext:getApp().managedObjectContext];
[fetchRequest setEntity:entityDesc];
// Sort by lastmodifieddatelocal
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"lastmodifieddatelocal" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
NSArray* instances = [getApp().managedObjectContext executeFetchRequest:fetchRequest error:&requestError];
NSMutableArray* responses = [[NSMutableArray alloc] init];
for (Response* response in instances) {
NSData *jsonData = [response.json dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary* dictionary = [HPSJSON getDictionaryFromDataWithoutSuccessTest:jsonData];
NSString* userEmailFromResponse = [HPSJSON getStringForKey: #"_useremail" inDictionary:dictionary];
NSString* formIdFromResponse = [HPSJSON getNestedIdForKey: #"_formid" inDictionary: dictionary];
if ([formId caseInsensitiveCompare:formIdFromResponse]==NSOrderedSame)
{
[responses addObject: response];
[indexSetOfAllEntries addObject:[NSNumber numberWithInt:responses.count-1]];
if ([activeUserEmail caseInsensitiveCompare:userEmailFromResponse]==NSOrderedSame)
{
[indexSetOfEntriesForLoggedOnUser addObject:[NSNumber numberWithInt:responses.count-1]];
}
}
}
NSMutableDictionary* results = [[NSMutableDictionary alloc] init];
[results setObject:responses forKey:#"responses"];
[results setObject:indexSetOfAllEntries forKey:#"allindexes"];
[results setObject:indexSetOfEntriesForLoggedOnUser forKey:#"indexesforactiveuser"];
return results;
}
The second method looks like this:
+(NSInteger)getCountOfResponsesWithForm:(NSString*)formId
{
NSError* requestError = nil;
NSString* activeUserEmail = getActiveUser().email;
NSFetchRequest* fetchRequest = [ [NSFetchRequest alloc] init];
NSEntityDescription* entityDesc = [NSEntityDescription entityForName:#"Response" inManagedObjectContext:getApp().managedObjectContext];
[fetchRequest setEntity:entityDesc];
NSArray* instances = [getApp().managedObjectContext executeFetchRequest:fetchRequest error:&requestError];
NSInteger countOfResponses=0;
for (Response* response in instances) {
NSData *jsonData = [response.json dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary* dictionary = [HPSJSON getDictionaryFromDataWithoutSuccessTest:jsonData];
NSString* userEmailFromResponse = [HPSJSON getStringForKey: #"_useremail" inDictionary:dictionary];
NSString* formIdFromResponse = [HPSJSON getNestedIdForKey: #"_formid" inDictionary: dictionary];
if ([formId caseInsensitiveCompare:formIdFromResponse]==NSOrderedSame)
{
if ([activeUserEmail caseInsensitiveCompare:userEmailFromResponse]==NSOrderedSame)
{
countOfResponses++;
}
}
}
return countOfResponses;
}
There is quite a lot of duplicate code here, and I feel I am abusing DRY to some extent. However, attempting to combine the methods into one method will introduce some complication that will somewhat obfuscate what each individual method does.
Can anyone offer advice on the most elegant way to implement the functionality that is encompassed on both methods? Stay with the two methods? Create one more complex method sprinkled with conditions? Break out into a different class?
Is their a relevant design pattern for this scenario? Thanks.
I would suggest:
identify common blocks (e.g., NSFetchRequest* fetchRequest..., NSEntityDescription* entityDesc);
for each common block, define a method that gets the proper arguments and does what is required of them;
call such method to improve on DRY, e.g.:
<getInstances>
<for each instance>
<extract data from instance>
<do your processing on data>
I would not think in terms of design patterns, rather in terms of refactoring in this case.
You could of course encapsulate all this in a class of its own, but I do not know the role played by the class presently containing your code, so I cannot say anything more detailed.
BTW, I tend to use design patterns at a higher level of abstraction (that is why they are called design pattern, although many are mostly useful at the architectural level). In this case some refactoring recipes (as you might find googling for them) are better suited I think.
I would consider Template design pattern.
It basically encapsultes basic behavior in template method inside the base class and conrete behavior is implemented inside underlying child classes that implements (or overrides) abstract methods defined in base class.
Interesting resource also here on SO: Objective-C - Template methods pattern?
Use design pattern Visitor or Command. This lets you decouple fetching and iterating over the responses from what action to take.
What you have here are two things: A preparation + loop + marshalling step and a processing step. One part is the same (the former) and one varies (the latter). So you want to factor out the latter, so you can avoid making several copies of the former.
You can do so either by making the processing step a dummy method that is overloaded in subclasses, or by passing in "something" that can perform the step. This "something" will usually be either a Command or a Visitor.
It all really boils down to whether you want to use inheritance or composition. Personally I tend to prefer composition.
I am not good with Xcode, so let me just write some logic instead.
It seems to me that your second method can be simplified by :
int getCountOfResponsesWithForm(String a)
{
Dictionary dic = getListOfResponsesWithForm(a);
return dic.length();
}
You might interested to read Facade Design Pattern.

Getting NSFetchedResultsController, NSSortDescription and sectionNameForKeyPath to work together

I'm currently working on an App that has a couple of Entities and relationships as illustrated below:
Item <<--> Category.
I am currently fetching Item instances and displaying them in sections using the item's category.name. In this case I can use a sort descriptor to sort the categories by name, which is pretty straightforward and working fine (relevant code below):
-(NSFetchedResultsController*)fetchedResultsController {
if (fetchedResultsController_ != nil)
return fetchedResultsController_;
NSManagedObjectContext *moc = [order_ managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Item" inManagedObjectContext:moc];
[fetchRequest setEntity:entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"category.name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
NSFetchedResultsController *controller = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:moc
sectionNameKeyPath:#"category.name"
cacheName:nil];
controller.delegate = self;
self.fetchedResultsController = controller;
[controller release];
[fetchRequest release];
NSError *error = nil;
if (![fetchedResultsController_ performFetch:&error]) {
// Error handling
}
return fetchedResultsController_;
}
My problem now is that I need to sort these categories not by name, but by a (NSNumber*) displayOrder attribute that is part of the Category entity. BUT I need the section titles on the tableview to continue to use the categorie's name.
If I set the sortDescriptor to use category.displayOrder and keep the sectionNameKeyPath as category.name, the section titles work fine but the sortDescriptor is simply ignored by the fetchedResultsController and the table sections are ordered by the category's name (not sure why??).
My next idea was to overwrite the displayOrder getter method but that didn't get me too far as the return types are different, plus I needed the actual displayOrder value for the section sorting.
So right now I have a solution which feels a bit clunky (code below), and I am wondering if there is a better way of achieving the same thing using the fetchedResultsController alone.
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
// The code below grabs a reference to first object for a given section
// and uses it to return the associated category name
{
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
NSArray *menuItems = [sectionInfo objects];
if ([menuItems count] > 0)
{
MenuItem *menuItem = [menuItems objectAtIndex:0];
NSString *categoryName = menuItem.category.name;
return categoryName;
}
return [sectionInfo name];
}
Am I missing something basic here?
Thanks in advance for your thoughts.
Rog
That's a perfectly good solution to the problem, Rog.
You certainly don't want/need to subclass NSFetchedResultsController.
#aroth, we don't have enough information to know the details of his object model, but the names certainly imply that category does not own item. Item has a category. His intent is to display a list of items, that is why he is fetching items.
As far as sorting goes, the documentation has this to say:
If the controller generates sections, the first sort descriptor in the array is used to group the objects into sections; its key must either be the same as sectionNameKeyPath or the relative ordering using its key must match that using sectionNameKeyPath.
In English (you may already know this Rog, but then again you may not and certainly people who search this later may appreciate the explanation), that means if you're using sections then the sorting on the NSFetchRequest must group all items in the same section together. This could be by making the first sort criteria be the field used as the section name, or it could be by making the first sort criteria be something else that results in the same sort of grouping.
The documentation doesn't specify what happens if you screw this up; it's possible it would just totally screw up the section names, repeat sections, skip sections, detect the situation and "fix" your sorting, or even just crash. Do any of your categories have the same displayOrder?
Your solution is certainly workable, and if you can't get it to work correctly sorting by displayOrder while titling sections by category.name it's probably your best solution.
Why are you fetching Item and not Category? If I understand your relationships correctly, Category owns a 1 to many relationship with Item, so in theory a Category instance should have an 'items' property that returns every Item in that category.
If that's the case, then you could simply fetch all of your categories, and then sort them by displayOrder. Then, forget about using sections in the NSFetchedResultsController itself. Instead, your associated tableView methods would look something like:
- (NSInteger)numberOfSections {
return [[self.fetchedResultsController fetchedObjects] count];
}
- (NSInteger)numberOfRowsInSection:(NSInteger)section {
Category* category = [[self.fetchedResultsController fetchedObjects] objectAtIndex:section];
return [[category items] count];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
Category* category = [[self.fetchedResultsController fetchedObjects] objectAtIndex:section];
return category.name;
}
In short, I think you are overcomplicating things by fetching Item instead of Category, and by trying to make the NSFetchedResultsController manage your section grouping for you. It is much simpler and requires much less code to just do the section management yourself.
You probably need to subclass NSFetchedResultsController and customize the section name functions. See the NSFetchedResultsController class docs on subclassing.