Dynamically setting fetchLimit for NSFetchedResultsController - iphone

I'm using am NSFetchedResultsController to populate data onto a UITableView.
It's a simple chat app and I want to load the latest 25 messages onto the table first and load more as the user scrolls up to see older messages (the chat message are in a ascending order).
I call a method that will setFetchLimit: for the NSFetchedResultsController in the willDisplayCell: like so....
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.row == 0)
{
[self performSelector:#selector(getMoreMessages) withObject:nil afterDelay:1.0];
}
}
when the first row of the UITableView has been displayed, getMoreMessages will try to reset the fetchLimit reload the UITableView like so.....
- (void)getMoreMessages
{
maxListItems += 25;
NSLog(#"set maxListItems: %d", maxListItems);
[self.resultsController.fetchRequest setFetchLimit:maxListItems];
[self._tableView reloadData];
}
However, it doesn't seem to be working, the table data will not change.
The initial NSFetchRequest is set like so...
NSFetchRequest *chatDataRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"ChatData" inManagedObjectContext:appDelegate.managedObjectContext];
[chatDataRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(key != 0 OR messageNo != 0) and matchNo = %d", matchNo];
[chatDataRequest setPredicate:predicate];
NSSortDescriptor *sortDescripter1 = [[NSSortDescriptor alloc] initWithKey:#"status" ascending:YES];
NSSortDescriptor *sortDescripter2 = [[NSSortDescriptor alloc] initWithKey:#"messageNo" ascending:YES];
NSArray *sortDescripters = [[NSArray alloc] initWithObjects:sortDescripter1, sortDescripter2, nil];
[chatDataRequest setSortDescriptors:sortDescripters];
[sortDescripters release];
[sortDescripter1 release];
[sortDescripter2 release];
[chatDataRequest setFetchLimit:25];
NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:chatDataRequest managedObjectContext:appDelegate.managedObjectContext sectionNameKeyPath:nil cacheName:[NSString stringWithFormat:#"%d_chat.cache", matchNumber]];
[chatDataRequest release];
fetchedResultsController.delegate = self;
NSError *error;
BOOL success = [fetchedResultsController performFetch:&error];
if(!success) NSLog(#"error: %#", error);
self.resultsController = fetchedResultsController;
And back to the question.
How can one dynamically change the fetchLimit for an NSFetchedResultsController?
Any hits would be awesome!
Thanks!

Instand using setFetchLimit, using setBatchSize, see below for detail answer.
The count of the fetchedObjects array might not what you want to do, since it does not update the changes from the persistent store. From NSFetchedResultsController documentation:
The results array only includes instances of the entity specified by the fetch request (fetchRequest) and that match its predicate. (If the fetch request has no predicate, then the results array includes all instances of the entity specified by the fetch request.)
The results array reflects the in-memory state of managed objects in the controller’s managed object context, not their state in the persistent store. The returned array does not, however, update as managed objects are inserted, modified, or deleted.
If you only want to fetch 20 objects, set the fetch limit of the NSFetchRequest. If you want only to keep 20 objects in memory, use setBatchSize of the NSFetchRequest object.

figured this one out.
looks like I have to run performFetch: after I change the fetchLimit. :D
[self.resultsController.fetchRequest setFetchLimit:maxListItems];
[self.resultsController performFetch:&error];

Related

How can I use NSFetchedResultsController to fetch results based on an entity's relationship?

I have 2 views that contain a UITableView each. They are both displayed at the same time, side by side, on an iPad.
I am using Core Data for all data. Both tables need to be edited (rows added, deleted, etc), so I'd like to use a NSFetchedResultsController in each view to handle all this for me.
The contents of the second table depend on what is selected in the first table. So, when selecting an item in the first table, that object is passed to the view with the second table (so I do already have access to the data that should go into the second table), but I'd like to try to use all the built-in handling of the NSFRC if possible.
The model is along the lines of: University (uniID, uniName, students) and Student (stuID, stuName, university). So the relationship is: University <-->> Student.
I'm using the following code in the NSFRC, but it's returning 0 results:
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
NSManagedObjectContext *context = appDelegate.managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Student" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]initWithKey:#"stuName" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"university == %#",self.selectedUniversity];
[fetchRequest setPredicate:predicate];
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
fetchedResultsController.delegate = self;
[sort release];
[fetchRequest release];
[theFetchedResultsController release];
return fetchedResultsController;
}
I would be most grateful if someone could at least point me in the right direction...
Have you remembered to performFetch: ?
i.e.
NSError *error;
BOOL success = [fetchedResultsController performFetch:&error];
Ok, so I solved the issue. The NSFRC wasn't being updated when the predicate term needed to change (i.e. a University had been selected), of course, because of:
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
Having the NSFRC recreated every time it's called (by removing the above code) doesn't work either, because the fetch needs to be executed after it's been created, which can't happen just before numberOfRowsInSection is called (as this method calls, and therefore recreates, the NSFRC).
So, I added a BOOL to the view called newFetchRequired which is set to YES every time a new University is selected. In the NSFRC, the above code should be changed to:
if (fetchedResultsController != nil && !newFetchRequired) {
return fetchedResultsController;
}
newFetchRequired = NO;
The fetch is then performed correctly (which calls and recreates the NSFRC):
[self.fetchedResultsController performFetch:&error];
I hope this helps anyone in a similar situation.
Thanks to Ashley for the alternative suggestion.
I don't know if you're still checking this and I'm not quite sure if I understood your question correctly.. but why do you create the NSFRC from scratch when you just want to change the predicate? Here's how I would do it:
When a new University is selected just add in the code right there:
NSFetchRequest *fetchRequest = [self.fetchedResultsController fetchRequest];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"university == %#",self.selectedUniversity];
[fetchRequest setPredicate:predicate];
NSError *error;
[fetchedResultsController performFetch:&error];
and you're done. (Sorry if this is totally not what you were looking for).
edit: You could also simply write this into a private function and call it every time a university is selected.

NSFetchResultsController w/ NSPredicate (relationship) vs. Basic NSFetchRequest (Pic Included)

Here is an image of the relationship I am debating:
I have a UIScrollView setup as a horizontal scroller that scrolls between 3 different UIViewControllers (containing a UITableView of course and the required delegate methods, etc.)
Each ViewController in the UIScrollView loads a UITableView of a specific MyObjectType.
(E.g. ViewController1 loads a tableview of all MyObjects where its type == MyObjectType.name)
Does this make sense? You'll notice I've setup an inverse relationship between the objects. A MyObjectType can have many MyObject's but a MyObject can only have a single MyObjectType associated to it.
When I first load one of the UIScrollView viewController's I need to determine what MyObjectType this UITableView is for. I have this working fine and I set the Table Header accordingly.
E.g. [type valueForKey:#"name"] where type is a fetched result NSManagedObject of MyObjectType.
The thing is I'm wondering, when I obtain this NSManagedObject of MyObjectType do I not also have access to a NSSet *array (ie. [type valueForKey:#"objects"]) which I can use as the UITableView's datasource? Would this work if after I add or delete an object I save the managedContext and then I always [tableView reloadData] ?
I'm guessing this would work, as long as I don't require the UITableView content to change and update dynamically as new MyObject of this type are added? For this we require a NSFetchedResultsController right?
Here is my code for loading ALL MyObject's into a UITableView (which works):
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"MyObject" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"creationDate" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:#"transientSectionDate"
cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
[sort release];
[fetchRequest release];
[theFetchedResultsController release];
return _fetchedResultsController;
}
Could someone PLEASE be as so kind to show my what actual NSPredicate declaration I need to correctly load ONLY MyObject's whose MyObjectType.name == #"XXXXXX"? Let's assume I already have a MyObjectType.name stored in a retained NSString inside the ViewController.
Thanks in advance!
The predicate format string would be:
#"ALL type.name=%#", typeName
However, since you do have a particular MyObjectType object, you already have direct access to the needed MyObject objects and don't have to waste time trying to fetch them. Just convert the set into a sorted array.
To keep apprised of ongoing changes while the table is active, implement observeValueForKeyPath:ofObject:change:context: in the tableview datasource object. Then send addObserver:forKeyPath:options:context: to that particular MyObjectType object like so:
[anObjectType addObserver:self
forKeyPath:#"objects"
options:(NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld)
context:nil];
Now, whenever the objects value of that paticular MyObjectType changes, the tableview's datasource will be notified and can change the table.
See Key-Value Observing Programming Guide for details.

NSFetchedResultsController custom sort not getting called

I am currently trying to populate a UITableView in my project from Core Data using NSFetchedResultsController. I am using a custom search with a comparator (although I have also tried a selector and had an identical problem):
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
/*
Set up the fetched results controller.
*/
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Object" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:20];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"objectName" ascending:YES comparator:^(id s1, id s2) {
NSLog(#"Comparator");
//custom compare here with print statement
}];
NSLog(#"Sort Descriptor Set");
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Object" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:#"firstLetterOfObject" cacheName:#"Objects"];
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
if (![fetchedResultsController performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return fetchedResultsController;
When I enter this tab, I have logged all over the program and found that the NSFetchedResultsController does not even enter the comparator block when fetching. It instead sorts it with some default sorting method.
If I delete and add an Object with an objectName, however, it then does enter the comparator block and correctly sort the table.
Why does the NSFetchedResultsController not sort using the comparator until the managed object model is changed?
Notes: I have tried also turning off caching, and/or performing a fetch in viewDidLoad:, but it seems that how many times I fetch does not matter, but when. For some reason it only uses my sorting after the object model has been changed.
There are a couple of things I can think of. First, though this may not be your problem, you cannot sort on transient properties. But more likely is that when sorting in a model backed by a SQL store, the comparator gets "compiled" to a SQL query, and not all Objective-C functions are available. In this case, you'd need to sort in memory after the fetch is performed.
EDIT: See this doc, specifically the Fetch Predicates and Sort Descriptors section.
I see the same problem and a way to work around it is to modify an object, save the change then restore it to its original value and save again.
// try to force an update for correct initial sorting bug
NSInteger count = [self.fetchedResultsController.sections count];
if (count > 0) {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:0];
count = [sectionInfo numberOfObjects];
if (count > 0) {
NSManagedObject *obj = [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
NSString *name = [obj valueForKey:#"name"];
[obj setValue:#"X" forKey:#"name"];
// Save the context.
[self saveContext];
[obj setValue:name forKey:#"name"];
// Save the context.
[self saveContext];
}
}
Sorry, but did you miss the final fetch part to your code snippet?:
NSError *error;
BOOL success = [aFetchedResultsController performFetch:&error];
Don't forget to release the request too:
[fetchRequest release];

Core Data: Fetch all entities in a to-many-relationship of a particular object?

in my iPhone application I am using simple Core Data Model with two entities (Item and Property):
Item
name
properties
Property
name
value
item
Item has one attribute (name) and one one-to-many-relationship (properties). Its inverse relationship is item. Property has two attributes the according inverse relationship.
Now I want to show my data in table views on two levels. The first one lists all items; when one row is selected, a new UITableViewController is pushed onto my UINavigationController's stack. The new UITableView is supposed to show all properties (i.e. their names) of the selected item.
To achieve this, I use a NSFetchedResultsController stored in an instance variable. On the first level, everything works fine when setting up the NSFetchedResultsController like this:
-(NSFetchedResultsController *) fetchedResultsController {
if (fetchedResultsController) return fetchedResultsController;
// goal: tell the FRC to fetch all item objects.
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Item" inManagedObjectContext:self.moContext];
[fetch setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
[fetch setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetch setFetchBatchSize:10];
NSFetchedResultsController *frController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetch managedObjectContext:self.moContext sectionNameKeyPath:nil cacheName:#"cache"];
self.fetchedResultsController = frController;
fetchedResultsController.delegate = self;
[sort release];
[frController release];
[fetch release];
return fetchedResultsController;
}
However, on the second-level UITableView, I seem to do something wrong. I implemented the fetchedresultsController in a similar way:
-(NSFetchedResultsController *) fetchedResultsController {
if (fetchedResultsController) return fetchedResultsController;
// goal: tell the FRC to fetch all property objects that belong to the previously selected item
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
// fetch all Property entities.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Property" inManagedObjectContext:self.moContext];
[fetch setEntity:entity];
// limit to those entities that belong to the particular item
NSPredicate *predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"item.name like '%#'",self.item.name]];
[fetch setPredicate:predicate];
// sort it. Boring.
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
[fetch setSortDescriptors:[NSArray arrayWithObject:sort]];
NSError *error = nil;
NSLog(#"%d entities found.",[self.moContext countForFetchRequest:fetch error:&error]);
// logs "3 entities found."; I added those properties before. See below for my saving "problem".
if (error) NSLog("%#",error);
// no error, thus nothing logged.
[fetch setFetchBatchSize:20];
NSFetchedResultsController *frController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetch managedObjectContext:self.moContext sectionNameKeyPath:nil cacheName:#"cache"];
self.fetchedResultsController = frController;
fetchedResultsController.delegate = self;
[sort release];
[frController release];
[fetch release];
return fetchedResultsController;
}
Now it's getting weird. The above NSLog statement returns me the correct number of properties for the selected item. However, the UITableViewDelegate method tells me that there are no properties:
-(NSInteger) tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
NSLog(#"Found %d properties for item \"%#\". Should have found %d.",[sectionInfo numberOfObjects], self.item.name, [self.item.properties count]);
// logs "Found 0 properties for item "item". Should have found 3."
return [sectionInfo numberOfObjects];
}
The same implementation works fine on the first level.
It's getting even weirder. I implemented some kind of UI to add properties. I create a new Property instance via Property *p = [NSEntityDescription insertNewObjectForEntityForName:#"Property" inManagedObjectContext:self.moContext];, set up the relationships and call [self.moContext save:&error]. This seems to work, as error is still nil and the object gets saved (I can see the number of properties when logging the Item instance, see above). However, the delegate methods are not fired. This seems to me due to the possibly messed up fetchRequest(Controller).
Any ideas? Did I mess up the second fetch request? Is this the right way to fetch all entities in a to-many-relationship for a particular instance at all?
You need to actually perform the fetch for the table view controller:
// ...create the fetch results controller...
NSError *fetchRequestError;
BOOL success = [fetchedResultsController performFetch:&fetchRequestError];

Dealloc'd Predicate crashing iPhone App!

To preface, this is a follow up to an inquiry made a few days ago:
https://stackoverflow.com/questions/2981803/iphone-app-crashes-when-merging-managed-object-contexts
Short Version: EXC_BAD_ACCESS is crashing my app, and zombie-mode revealed the culprit to be my predicate embedded within the fetch request embedded in my Fetched Results Controller. How does an object within an object get released without an explicit command to do so?
Long Version:
Application Structure
Platforms View Controller -> Games View Controller (Predicated upon platform selection) -> Add Game View Controller
When a row gets clicked on the Platforms view, it sets an instance variable in Games View for that platform, then the Games Fetched Results Controller builds a fetch request in the normal way:
- (NSFetchedResultsController *)fetchedResultsController{
if (fetchedResultsController != nil) {
return fetchedResultsController;
}
//build the fetch request for Games
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Game"
inManagedObjectContext:context];
[request setEntity:entity];
//predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"platform == %#",
selectedPlatform];
[request setPredicate:predicate];
//sort based on name
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name"
ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
//fetch and build fetched results controller
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:nil
cacheName:#"Root"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[sortDescriptor release];
[sortDescriptors release];
[predicate release];
[request release];
[aFetchedResultsController release];
return fetchedResultsController;
}
At the end of this method, the fetchedResultsController's _fetch_request -> _predicate member is set to an NSComparisonPredicate object. All is well in the world.
By the time - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section gets called, the _predicate is now a Zombie, which will eventually crash the application when the table attempts to update itself.
I'm more or less flummoxed. I'm not releasing the fetched results controller or any of it's parts, and the only part getting dealloc'd is the predicate. Any ideas?
EDIT:
As a test, I added this line to the Fetched Results Controller method:
[fetchedResultsController.fetchRequest.predicate retain];
And now it doesn't crash, but that seems like a patch, not something I should be doing.
You shouldn't be releasing your predicate variable. You didn't invoke new, alloc, retain, or copy (This is the "narc" rule) to create the predicate, so you are not responsible for releasing it. That's where your zombie is coming from.