Slow loading of UITableView. How know why? - objective-c

I have a UITableView that show a long list of data. Use sections and follow the sugestion of How to solve slow scrolling in UITableView .
The flow is load a main UITableView & push a second selecting a row from there.
However, with 3000 items take 11 seconds to show. I suspect first from the load of the records from sqlite (I preload the first 200). So I cut it to only 50.
However, no matter if I preload only 1 or 500, the time is the same.
The view is made from IB and all is opaque.
I run out of ideas in how detect the problem. I run the Instruments tool but not know what to look.
Also, when the user select a cell from the previous UITable, no visual feedback is show (ie: the cell not turn selected) for a while so he thinks he not select it and try several times. Is related to this problem.
What to do?
NOTE: The problem is only in the actual device:
iPod Touch 2d generation
Using fmdb as sqlite api
Doing the caching in viewDidLoad
Using NSDictionary for the caching
Using a NSAutoreleasePool for the caching part.
Only caching the row ID & mac 4 fields necesary to show the cell data
UIView made with interface builder, SDK 2.2.1
Instruments say I use 2.5 MB in the device

The -[FMResultSet next] call can be a very expensive call to make, depending on the data that's getting loaded. It'd during this call that sqlite is actually going to the database, finding the next row to return, and giving you back the appropriate fields. It's not just an enumerator.
You might want to consider pre-caching all of the data before actually displaying the table. This means that you would do all of your FMDB calls before the table gets shown on the screen.
If that takes too long, you might want to show the tableview with its initial rows, and then use NSOperations or just a second thread to load the data in the background and cache it that way.

Without seeing any code, I think I would be inclined to think that you need to index you tables.
You should see an enormous speed up by adding an index on the attributes you query on. You can do this by using the CREATE INDEX SQL command.

Related

Issue in lazy loading the table view

I am badly struck in a issue where I am trying to populate the nestableview lazily. Below is my approach.
I have created a custom class PRIList where it has an instance of array to manage the models.
I have bound the priList.items to the array controller in the xib where items is not an instance in PRIList but to support lazy loading I have implemented the methods countOfItems and objectInItemsAtIndex:.
Initialy when I populate the PRIList I populate few objects (say 50) with valid objects and rest with the faulty objects. In the objectInItemsAtIndex I check if the item at particular index is valid or faulty. If it is faulty I fetch next set of 50 objects.
What I understand is NSArrayController calls the method objectInItemsAtIndex for only the visible rows in the table view. But the problem here is as soon as set the PRIList the objectInItemsAtIndex method is called for all the objects. This is even called when some selection is changed in table view (the stack trace shows this method is called from [_NSModelObservingTracker startObservingModelObjectAtReferenceIndex])
Basically I want to fetch the records whenever the user scrolls down in the table view.
I followed the same approach in a different project in Lion. It worked there. Currently I am in Mavericks.
I tried overriding the isCompatibleWithResponsiveScrolling in the custom table view and returned it to NO. Still no luck.
Any help is very much appreciated.
First, have you assigned or bound the sort descriptors of the array controller? Or set any columns to automatically generate sort descriptors? (I'm not sure that latter is relevant. It depends on whether the column is sorted by default.)
In any case, if the array controller feels the need to sort the objects in order to arrange the objects, then it will need to load all of the contents. I was under the impression that it always does so, anyway, although you report that it works.
For an issue like this, I'd recommend that you go for full manual control. That means not using bindings or an array controller. Use a data source.

Is creating an NSString or NSArray expensive?

I'm working on optimizing the performance of my TableViews, and was wondering how expensive it is to create simple objects like a string or array within cellForRowAtIndexPath. The other option, of course, is to create the object as an ivar and set it each time a new cell gets loaded.
Which is better, creating a new object for each cell, or resetting the same object for each cell?
Note: I'm using these objects to do a number of different things, for example I'm creating a string to check the length of text I'm downloading from a server before passing it to the cell.
Creation of simple strings and arrays is not going to have an effect on the performance of your table. Creating views is expensive, and drawing them is also expensive - these are the areas you need to optimise for good scrolling.
Profile in instruments using the time profiler and core animation FPS instrument to be sure. This will highlight hotspots in your code, or alternatively give you a 60fps scroll rate*, in which case your work is done!
*on the device, of course

Memory limit to number of items in table view

Is there a memory issue regarding the number of items in the list I am displaying in my table view? If the user keeps adding items will the application eventually crash or something due to memory issues?
To answer your questions:
There's no black and white answer to this. To try my best to explain, think of UITableView as sort of like a visual data array. In fact, most people (myself included) use a source data object like an NSArray or an NSDictionary to provide the display data for a UITableView. All the memory limitations that would apply to these objects (arrays and dictionaries) apply to your UITableView, assuming your UITableView is set up properly and you're using the recommended techniques for reusing cells. What this boils down to is: Is it possible to have a very large UITableView? - Yes. How long though, this I don't know. I've created UITableViews with complex subclassed cells and 200 rows and they worked fine. It depends on how you set up the table and the data source you're using. Bear in mind again that the limitation is due to the data source. Have an extremely large array and eventually your device will throw a memory warning. As a best practice, use pagination. There are tonnes of tutorials online to enable paging on UITableViews. Lazy load your images (if any) if they are being downloaded.
Yes you can. You can do lots of amazing things if you're creative enough while subclassing UITableViewCells. Otherwise, you can use the standard UITableViewCell as well. There are two labels on there: The textLabel and the detailTextLabel. Use these two to display the data you want.
Your UITableViewDelegate has a didSelectRowAtIndexPath method which you can implement. As long as your view controller housing the UITableView is set to be it's delegate, it will respond to didSelectRowAtIndexPath.
Just empty the array you're using as a data source (bear in mind that your numberOfRowsInSection data source method MUST use the array count) and call reloadData on the UITableView
EDIT: The question got edited, so only point 1 from the above 4 points applies to the question :) The others are nice to know though
Nothing table-specific, but yes, you will run out.
The most important thing is probably to use dequeueReusableCellWithIdentifier: in your tableView:cellForRowAtIndexPath: delegate method. This basically cuts down memory usage to the number of visible cells (plus one being scrolled into view).

NSTableViewDataSource or NSArrayController?

I need to load data dynamically as a user scrolls through an NSTableView. For example, the table might display 50 rows, and as it scrolls further I need to fetch more data from the network. The number of of objects/rows is known beforehand, so I want the table to have the right number of rows from the start, but showing empty cells while data is loading.
I'm using Core Data so it's easy to connect the table to my model using bindings. This would also take care of cells being updated as data comes in and is parsed. I've tried to figure out how I could do this by subclassing NSArrayController but from what I can tell there is no information flowing from the table to the controller about which rows actually need data. Therefore, I'm thinking of implementing NSTableViewDataSource instead, where I can easily check if the table has scrolled beyond the rows for which data is available. On the other hand, I don't know if I will get cells automatically updating as easily with this solution.
In case anyone comes across this, here's my own answer:
Yes, you need to implement NSTableViewDataSource on a controller, observe changes on the data and call reloadData manually on the table when changes occur. The main reason for doing this is that you can defer loading of data until it's actually needed (when the table view scrolls). Using the data source protocol keeps you informed of which indexes are requested.

NSTableView requesting Data too early

I've run into a bit of an issue with NSTableView. The problem is that the table view is requesting data too early (it immediately requests the data from the controller), as in, the data is not there when the table view asks for it. Other methods, which gather the data to be shown in the table view haven't finished executing by the time NSTableView requests the data.
Hope that makes some kind of sense! I'm using Objective-C and doing mac dev (not iphone) btw.
Anyway, is there any way around this?
Thanks in advance.
The NSTableView is asking for the data likely because you have told it that there are say 20 rows of data. Once it knows there are 20 rows, it will start asking for it.
If you are using the NSTableView data source Delegate, its easy, just return 0 for the number of rows until everything is ready - then return the number of rows that there are.
IE you need these calls in the delegate:
– numberOfRowsInTableView:
– tableView:objectValueForTableColumn:row:
If you are not using the NSTableViewDataSource, and have wired up the controller in Interface Builder, then you will have to somehow only give the controller the data when its all ready.
Sounds like you're populating the table view asynchronously? If so you could call reloadData or reloadDataForRowIndexes:columnIndexes: on your table view in the method that handles the response to refresh the table view once the data has loaded.