UITableView with huge(probably 1million entries)data in iphone - iphone

I am developing an application which requires loading of more than 1 million entries through infinite scrolling in a tableview. Each time request will be sent for 1000 entries and once data is downloaded and parsed through JSON library the table is reloaded. I have implemented this through CoreData with "setFetchBatchSize = 1000".
StreamModal *modal = [[StreamModal alloc]init];
StreamModal *modal = [NSEntityDescription insertNewObjectForEntityForName:#"StreamModal" inManagedObjectContext:managedObjectContext];
if([self isNotNull:[streamDataDict objectForKey:#"_id"]])
modal.stream_id = [streamDataDict objectForKey:#"_id"];
-(void)reloaData{
#try {
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
NSLog(#"ferchresults count %d",[[_fetchedResultsController fetchedObjects]count]);
}
#catch (NSException *exception) {
NSLog(#"exception raised in reloadData in streamViewController class %#",exception);
}
#finally {
}
}
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"StreamModal" inManagedObjectContext:appDelegate.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:1000];
//[fetchRequest setFetchLimit:2000];
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:appDelegate.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
fetchRequest = nil;
theFetchedResultsController = nil;
return _fetchedResultsController;
}
Here is the code i am using, when ever connectionfinishedloading data i am populating the data into NSManagedObject Class(StreamModal) and then calling reload data.Here the problem is the app is getting memory exceptions after i loaded 12000 entries in table and getting crashed. how can i load all entries without memory exception. i am new to CoreData concepts and have read the core data concepts through developer guide, but i didn't find any info related to memory handling. Please help me.

I hope you are using ARC? Because you're not releasing any initalized objects. (If not this is your answer.)
But anyway: Have you tried to use Instruments to see, which objects are increasing the memory footprint at most? That would be a good starting point.

shiva inturi,
First, I want to echo other comments that working with a single table view of a million items is really a bad user experience.
To your question:
What are you doing when the memory warning comes?
At minimum, you should be going through the objects array and trim the object graph. This is done with -refreshObject:mergeChanges:. You should also take care to not traverse your array very far. I would start from your visible objects and work both backwards and forwards until you start hitting faulted objects, by testing with -isFault.
Andrew

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.

TableViewController's Table View not displaying until the data is loaded

I'm facing a strange issue. I have a method which populates an array with some data (fetchData) (quite a lot actually and it's a bit slow). I'm using the array to build the rows of the table.
I've tried calling fetchData in a number of places in my code, at various times in the construction of the view and I always seem to get the following: a black screen which is shown until the data from the array is loaded. I've called fetchData from the following:
(void)viewDidLoad;
(void)viewWillAppear:(BOOL)animated;
(void)viewDidAppear:(BOOL)animated;
Since I'm using a navigation view controller, having the app appear to hang is pretty bad looking since it gets stuck on a black screen. What I was hoping my code would achieve was displaying an empty table, with a progress indicator until the data is loaded - then refresh. Unfortunately I'm not getting this far since the view isn't being loaded no matter where I call fetchData.
Help appreciated!
P.S. To get around this problem I even tried using a TTTableViewController, but the Loading view is never displayed. Typical. sigh
Your load method must be blocking the UI. You should move it to another thread and let the data load there. You can instantiate the thread in viewDidLoad.
This is a skeleton code for that you need to (using GCD)
dispatch_queue_t downloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(downloadQueue, ^{
... Move all the loading part here.
dispatch_async(dispatch_get_main_queue(),^{
... Do all the UI updates, mostly, [tableView reloadData];
})
})
It possible that you could add a timer to delay the call somewhere in your viewDidAppear method. For example:
- (void)viewDidAppear:(BOOL)animated {
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(fetchData) userInfo:nil repeats:NO];
}
This will give your app time to load the UI and start your loading screen, then start fetching the data later. You can also try fetching the data in a background thread if you would prefer to go that route
I was having the same issue with a table view not loading initially, but it worked n another .m file I had. Here is the one that worked:
add this to your viewDidLoad:
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
and this to the implementation block:
#pragma mark -
#pragma mark Fetched results controller
- (NSFetchedResultsController *)fetchedResultsController {
if (fetchedResultsController == nil) {
// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"OMFrackinG" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
// grouping and sorting optional
//NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"country" ascending:YES];
NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc] initWithKey:#"state" ascending:YES];// was name
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1,sortDescriptor2, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
propriate.
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:#"state" cacheName:nil];//#"state"
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor1 release];
[sortDescriptor2 release];
[sortDescriptors release];
}
return fetchedResultsController;
}

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];

UITableView not updating DataSource after change to NSFetchedResultsController

I have an UITableView populated by a NSFetchedResultsController. The initial fetch works fine. I can add, remove, modify, etc with zero problems.. But I want to add user-defined sorting to the table. I am doing this by changing the NSFetchedResultsController to use a different sortDescriptor set, and a different sectionNameKeyPath. Here is the code where I change the fetch:
-(void)changeFetchData {
fetchedResultsController = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Object" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSString *sortKey = #"sortKey";
NSString *cacheName = #"myNewCache";
BOOL ascending = YES;
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:sortKey ascending:ascending];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:sortKey cacheName:nil];
self.fetchedResultsController = aFetchedResultsController;
fetchedResultsController.delegate = self;
[aFetchedResultsController release];
[fetchRequest release];
[sortDescriptor release];
[sortDescriptors release];
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(#"Fetch failed");
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
[self.tableView reloadData];
}
When I call this method, it works great. The table immediately re-orders itself to use the new section info, and the new sorting parameters. But if I add or remove items to the data, the TableView doesn't update its info, causing a crash. I can NSLog the count of the total number of objects in the fetchedResultsController, and see it increase (and decrease) but if I NSLog the return values for numberOfRowsInSection to monitor a change there, the method gets called, but the values don't change. The get the following crash (for addition, but the deletion one is similar)
Invalid update: invalid number of rows in section 2. The number of rows contained in an existing section after the update (3) must be equal to the number of rows contained in that section before the update (3), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted). with userInfo (null)
If I restart the app, I see the added item, or do not see the deleted item, so I am modifying the datasource correctly.
Any ideas?
It's possible that the old controller is still alive. If so, it might still be calling the tableview controller as its delegate and activating the table update using it's own data.
I would suggest logging the fetched results controller object in numberOfRowsInSection to confirm that it using the new controller. You should set the old controller's delegate to nil before assigning the new one.
On one occasion, adding:
- (void)viewDidDisappear:(BOOL)animated
{
self.fetchedResultsController.delegate = nil;
}
Solved a similar issue which arises when number of rows changes but data held is still different and not up-to-date.

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.