I have a tabbed application, that has 2 tabs with 2 UITableView.
I also have 2 NSFetchedResultsController type objects, but both of them are on the same entity with different ordering and different fetch limit.
When I download more objects from the internet and insert them to the database, my NSFetchedResultsController type objects will ignore the fetchLimit. For the first one I set a fetchLimit of 10 and for the second I set a fetchLimit of 50. Initially I have 10 objects in the database. Everything is fine. After I download more 40 objects the first one also loads the more 40 objects, but it has a fetchLimit of 10.
What's wrong with this?
NSFetchedResultsController ignoring fetchLimit in case if it observers context changes.
I think that it's not so simple operation to correctly update table via momc observation, when you're restricted to fetchlimit.
SOLUTION #1
So, in case if big update has been occured, you should re-fetch data.
So you should do something like this in FRC delegate:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
if (bigChangesPeformed) {
NSError * error;
// Re-fetching to get correct fetch limit
[self.fetchedResultsController performFetch:&error];
if (error) {
// bla-bla-bla
}
[self.tableView reloadData];
}
}
The fetch limit of NSFetchRequest is the limit used when performing fetches from the data store. It's not intended to limit the size of the fetched results array. If you want to limit the total number of cells displayed in your tableview, you should probably use UITableView's tableView:numberRowsForSection: to limit it.
If you really want to set a limit of only 10 items displayed (like showing a Top 10 list), there's probably no real reason to use more than 1 section either. If you restrict the number of sections in your tableView to 1 using numberOfSectionsInTableView: and the number of rows in that section AT MOST 10 with tableView:numberRowsForSection:, your table will always show up to 10 records, regardless of how many records are added into the data store.
You can use sort descriptors to order the objects managed by your NSFetchedResultsController instance, which I imagine is useful for displaying a Top 10 list.
Related
i got a UITableview with sections.
The sections are handled by Core Data's NSFetchResultsController, however this is less important concerning my problem.
I'd like to have a predefined amount of rows in my UITableview. Let say i start the application, i'd like to see only 10 rows, even if i got 14 entries in Core Data. When i click a button "load more.." the next 10 rows should be loaded (loading is implemented i just need to figure out a way to display the right amount of rows). If i got 10 rows at the beginning and in the first section are 7 and in the second are 5 then the last two rows of the second section should not be display until i click the load more button to show the next 10. So i think i have to implement this logic in numberOfRowsInSection and numberOfSectionsInTableView but i need a hint from you how to solve this...
Basically i would need a loop which loops through the sections. If the amount of rows i want to display is reached i return all sections until this point to be displayed. Then i would need to retrieve all the rows of this sections and the rows of the sections before to calculate the numberOfRowsInSection for each section...
Anyway somebody who can help me implement this ?
Thanks in advance
Perhaps a good way to solve this is to use the fetched results controller. You can try setting the fetchLimit on the fetched results controller's fetchRequest. If the sorting of your FRC is correct, it should retrieve the first n objects.
-(void)loadRows:(NSUInteger)numberOfRows {
NSFetchRequest *request = self.fetchedResultsController.fetchRequest;
request.fetchLimit = numberOfRows;
NSError *error;
[self.fetchedResultsController executeFetchRequest:request error:&error];
}
First off I sincerely apologize if this has been answered. I have been battling with this for too long. I would appreciate any kind of help.
I am developing an application that gets data asynchronously from different web sources and parses it into objects that are handled by my main Model. This model provides information for a UITableView. When data is received and parsed, i need to modify the number of sections and rows in the table view. I first receive the data that decides the number of sections, and then asynchronously data that modifies the number of rows in each section and their content. I have the table views data sources set up.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return [self.sijainti.pysakit count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
//if ([[[self.sijainti.pysakit objectAtIndex:section] ajatStr] count] == 0) return 1;
NSLog(#"ajatStr of section: %d count: %d",section, [[[self.sijainti.pysakit objectAtIndex:section] ajatStr] count]);
return [[[self.sijainti.pysakit objectAtIndex:section] ajatStr] count];
}
The object names are in Finnish (long story). The second line of numberOfRowsInSection was commented to ease debugging. Now, because the data is dynamic i need to update the interface when i have data ready for display, or the user wont know about it until they scroll away and back again. I have a method for this:
- (void)updateRowWithCell:(SCPysakki *)valmisPysakki {
if (!valmisPysakki.isInArray) return;
int section = [self.sijainti.pysakit indexOfObject:valmisPysakki];
[self.tableView beginUpdates];
NSIndexSet *sectionSet = [NSIndexSet indexSetWithIndex:section];
[self.tableView reloadSections:sectionSet withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
}
This is called only after the number of sections is known. I do NOT want to use reloadData due to efficiency, and animation. I used to have this set up so that i had only rows and was just drawing rows inside of the rows. But once the rows became taller than the iphone screen, it became very unclear, because you couldn't see the title. So i made them sections, and the rows drawn real rows. Then i was suddenly faced with a problem. reloadRowsAtIndexPaths: withRowAnimation: worked absolutely fine but using reloadSections: withRowAnimation: gave me this exception:
* Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-1914.84/UITableView.m:1037
2012-09-17 04:11:27.769 pysappi[2351:f803] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 1. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (0), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
I was able to eliminate this by adding the [self.tableView endUpdate] and [self.tableView endUpdate] to the update method. But oh no, that only works if I:
Use reloadData after receiving the data deciding the number of sections (which in turn shortens the tableview so that after the whole update the user at the top of the table after every single update. I would rather update the sections dynamically, without using reloadData, except when removing Sections, but if I try to do this i get the exception again.)
update the sections after receiving data for only one Row (so if I try to update all the rows in the section once I've received all their data, I get the exception again.)
Now this creates a situation where creating a good user interface becomes impossible. I'm convinced that the underlying problem is not solved with the start and end updating calls, but that just fixes it for some very very odd reason.
I also tried adding the rows manually with insertRowsAtIndexPaths: and updating them manually also, but this returns the exact same error. During a long debugging progress i have been able to determine that this exception rises after cellForRowAtIndexPath: gets called and it returns a cell for one of the new rows that was added during the updating, even though the numberOfRowsInSection should have updated the amount of rows. I also suppose that if those few circumstances apply this is why the start and end updating methods make the row updating work.
The only thing that has worked outside of what i explained above, is using only reloadData, but that is just practically impossible considering the amount of updating.
This became a lot longer than i thought, and its 4.30 AM. Not the first time I'm up this late thanks to this exception. If you need more information ask. Thank you in advance.
(Answered in the comments. Converted to a community wiki answer. See Question with no answers, but issue solved in the comments (or extended in chat) )
#deleted_user wrote:
number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (0), - you had a section with zero rows and did an update with a row - cocoa didnt see that as an insert and failed you
I want a thorough list regarding comparison between the two. Things I have known:
executeFetchRequest:
Message sent to MOC
Return an array of managed objects
Goal: fetch objects from persistent store to MOC
With table view: has nothing to do with table view
Frequency: often used in a loop, so could be called many many times
performFetch:
Message sent to FRC
After calling it, use fetchedObjects to return an array of managed objects
With table view: FRC is specifically for keeping managed objects and table view rows in sync, and use performFetch to initialize that process.
Frequency: often only once. Unless fetch request of FRC changes, no need to call performFetch a second time
Please correct me if I am wrong and append the list. Thank you.
About executeFetchRequest:
Message sent to MOC
Yes
Return an array of managed objects
Yes, but you can also change the type of results you want to retrieve. In NSFetchRequest you can set a different result type with:
- (void)setResultType:(NSFetchRequestResultType)type
where NSFetchRequestResultType can be of different types. Taken from Apple doc:
enum {
NSManagedObjectResultType = 0x00,
NSManagedObjectIDResultType = 0x01,
NSDictionaryResultType = 0x02
NSCountResultType = 0x04
};
typedef NSUInteger NSFetchRequestResultType;
Goal: fetch objects from persistent store to MOC
Yes, creating a NSFetchRequest and performing a request, it the same as creating a SELECT statement in SQL. If you also use a NSPredicate it's the same as using SELECT-WHERE statement.
With table view: has nothing to do with table view
Yes, but with retrieved data you can populate a table
Frequency: often used in a loop, so could be called many many times
It depends, on what you want to achieve. It could be within a loop or not. Executing the request within a loop could have impact on performance but I would not be worried on that. Under the hood Core Data maintains a sort of cache mechanism. Every time you perform a request, if data are not in the cache, Core Data executes a round trip on your store (e.g. sql file) and populate the cache with the objects it has retrieved. If you perform the same query, the round trip will not performed again due to the cache mechanism. Anyway, you could avoid to execute a request within the run loop, simply moving that request outside the loop.
About performFetch:
Message sent to FRC
Yes
After calling it, use fetchedObjects to return an array of managed
objects
Yes, but you can also retrieve an object with [_fetchedResultsController objectAtIndexPath:indexPath]; if you are populating a specific cell within a table.
Here I really suggest to read a nice tutorial on NSFetchedResultsController
With table view: FRC is specifically for keeping managed objects and
table view rows in sync, and use performFetch to initialize that
process.
Yes, a NSFetchedResultsController works in combination with a NSManagedObjectContext for you. Furthermore, it enables lazy loading of data. Suppose you have 1000 elements you retrieve and you want to display them in a UITableView. Setting a request for a NSFetchRequest like:
[fetchRequest setFetchBatchSize:20];
and using it with an instance of a NSFetchedResultsController, it allows to load 20 elements at first. Then when you scroll, other 20 elements are loaded, and so on. Without a NSFetchedResultsController you must implement this behavior manually. Refer to the tutorial I provided for further info.
Frequency: often only once. Unless fetch request of FRC changes, no
need to call performFetch a second time
It depends on what you want to achieve. Most of the time you could call it once.
Hope that helps.
Edit
You have to call performFetch explicitly. I like to create a property for NSFetchedResultsController in my header file (.h) like
#property (nonatomic, strong, readonly) NSFetchedResultsController* fetchedResultsController;
and synthesize it in your implementation file (.m) like
#synthesize fetchedResultsController = _fetchedResultsController;
Then always within the .m file override the getter to create an new instance of it:
- (NSFetchedResultsController*)fetchedResultsController
{
// it already exists, so return it
if(_fetchedResultsController) return _fetchedResultsController;
// else create it and return
_fetchedResultsController = // alloc-init here with complete setup
return _fetchedResultsController;
}
Once done, within your class (for example in viewDidLoad method) use it like
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error]) {
// Handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
You are comparing the wrong elements. NSFetchedResultsController uses the NSManagedObjectContext to perform the fetch, and under proper configuration, monitors the changes in the managed object context to verify the status of the fetch properties it is monitoring, but the actual fetches are done by the context. On both cases, NSManagedObjectContext does the fetch. The difference being that, using the NSManagedObjectContext directly, you get an NSArray type of object (the actual runtime class is different than an array you get using [NSArray array]), while NSFetchedResultsController has a different purpose (have a collection of results and monitor changes to the records and entity on its fetch request). In other words, NSFetchedResultsController works using the context, but it works different than just a simple collection of objects.
One observation: you shouldn't be using executeFetchRequest inside a loop, especially calling it "many many times". Each fetch has its performance cost. You can call executeFetchRequest once, and do a loop to check the result.
I've been trying to work out how to use table views and I'm a little stuck if I'm honest. I wanted to use a tableview with a limited number of rows (say 50 max). It starts of empty, with 0 rows. Then I wanted to do something along the lines of:
[self logMessage:#"Waiting for response"];
Which inserts a new row at the bottom with the above text. If I do another call to this pseudo function:
[self logMessage:#"Server response received"];
It should insert yet another new row below the previous row, and ensure it is visible. Once the above limit of 50 is reached, and a new message is inserted, I wanted the oldest message to be removed. All of this would be scrollable, with the latest being visible by default.
Am I looking at the right thing to do this? Eventually, I was hoping to have this in a nice little drawer below the main window, which I can then toggle from the main menu if needed. But as I said, I can't work out how to use a table view properly, it doesn't seem to be as straight forward as other objects are.
Any example code would be greatly appreciated!
Since log viewer is a read-only application of a UITableView, the way you do it is rather straightforward once you understand the basics. Recall that table views rely on their data models to provide them with the correct information that needs to be displayed.
A data model for "the last fifty lines of log" could be as simple as an NSMutableArray: use insertObject:atIndex: to add lines, and removeLastObject to remove the "overflow" lines, like this:
NSMutableArray *logLines = [NSMutableArray array]; // <<== this goes into the init method
-(void) addLogLine:(NSString*)line {
[logLines insertObject:line atIndex:0];
while (logLines.count > 50) {
[logLines removeLastObject];
}
}
Now you can use logLines as your table's "model": the data provider can tell how many lines there are by looking at logLines.count; the content of each row in the table will be the object at the corresponding index in logLines, and so on. Take a look at the UITableView section of your favorite iOs tutorial for the "boilerplate code" that needs to be written in order to display array elements in a UITableView.
I already asked a similar question but still can't get it to work. I have a core data database with 30k+ records. I am trying to fetch it into a tableview. I watched the WWDC presentation on core data and whatever the presenter suggested it didn't work for me. I deleted most of the fields from that table and only left like 4 which contains nsstring and date. I used setFetchBatchSize with different numbers and still when using instruments the fetch controller was fetching all 30k records. I watched it over and still not sure what i am doing from to lower the fetch. I used a separate thread and showed svprogreshud but that made it even slower i guess because the thread is not running right away.
Also The data is divided 12 sections and i have a field in core database for it with one character to set up sections.
I also set -com.apple.CoreData.SQLDebug 1 and when running on the device it showed 3sec loading that fetch request with all the records but showed
CoreData: sql: pragma cache_size=200
CoreData: annotation: total fetch execution time: 3.1087s for 36684 rows.
Here is some of the code
-(void)useDocument{
if (self.peopleDatabase.documentState == UIDocumentStateClosed){
[self.peopleDatabase openWithCompletionHandler:^(BOOL success){
[self setupFetchResultsController];
}];
} else if (self.peopleDatabase.documentState == UIDocumentStateNormal){
[self setupFetchResultsController];
}
}
-(void)setupFetchResultsController
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"people"];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"pplicon" ascending:YES]];
[NSFetchedResultsController deleteCacheWithName:nil];
self.fetchedResultsController = nil;
[request setFetchBatchSize:50];
//[request setFetchLimit:100];
self.fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:request managedObjectContext:self.peopleDatabase.managedObjectContext sectionNameKeyPath:#"pplicon" cacheName:#"peopleCache"];
}
Thank you in advance!
UPDATE
Jody thank you again for answering.
I went to the core data setup to the table and pplicon indexed is checked. Do i need to check anything else. I followed the directions for the wwdc video and ran instruments in core data fetches it shows NSManagedObjectContext executefetch .. Fetch Count 36684 and then another one with Fetch Count 12 Also in sampler the most time spent in peopleviewcontroller setupfetchResultsContoroller 363.75ms and UIAplication Run 287.6ms. In activity Monitor the app takes up 91mb real memory. Is there anyway to improve this time? What else can i check and see where the hang up is?
As far as the sections. I have a character in the database pplicon which used to show for section and the data is sort by it.
Data Model for now has one Entity with 5 Attribues as string and 1 Attribute as Date
I think I have commented on a very similar question in the past. You absolutely must run Instruments and see what it says. It will tell you exactly where it is spending its time.
You are using sections, which can be expensive. Is your section attribute "pplicon" setup in the database as an Index? If not, you will most definitely have to scan all items in the database. The only chance you have of not scanning all records is to make it an index. The internals may be able to just use an index table to get the unique index values. However, it still has to return section objects.
Again, you must turn on instruments while testing so you can see exactly where you are spending your time.
Are section names computed? When section names are computed, Core Data automatically fetches all of them and invoke section name to get sorting right.