Using performSelectorInBackground to load UITableViewCell image in background, performance - iphone

I have a method for loading images for UITableViewCell in the background. I use performSelectorInBackground. The problem is these threads are finishing and loading images even though they may not be on the screen anymore. This can be taxing on resources, especially when the use scrolls quickly and lots of cells are created. The images are fairly small and being loaded from the disk (sqlite db), not from a URL.
I've put code in the cell to check to see if it's the most recently displayed cell and I don't load the image if it's not. This works, but it's still creating the threads even though the "expensive" work of loading the image from disk isn't being executed unless it's the most recent cell.
The question is, what's the best way to deal with this? Should I kill existing threads each time the UITableViewCell is reused? How do I go about killing threads invoked by performSelectorInBackground?
Any other suggestions on how to handle this are appreciated.

Have you looked at EGOImageView?

You might consider just loading one image at a time. You can still do it on a thread but serialize the loads so as to not over-burden the system.
You could add the visible cells to an array when they become visible, when cells become invisible you can remove them from the list (or just check if they are visible at convenient times). You could also try deferring the load for a brief amount of time to avoid loading an image that is just scrolling by. The thread would pop the first item from the list and load it, then queue another load.
For threading technologies you could have a look at Operation Queues or make a dedicated thread with NSThread. The Concurrency Programming Guide provides a good overview. Although there is nothing wrong with the method you are using.
Sharing data between threads will also require some form of locking to avoid simultaneous access.

Related

run a process in the background while user can still use the UI

I am attempting to run a database fetch process in the background without locking the user interface.
Currently I have a button that does this, but I would like it to be automatic so that it can get more results as user is browsing current results.
Here is the code that the button does, I would like to make this automatic and not lock the UI. Also if there is a way to pause the process, but continue where it left off if user goes to another screen that would also be very useful.
Thanks in advance!
-(IBAction)continueUpdatingResultsButtonPressed:(UIButton*)sender{
[findMoreButton removeFromSuperview];
[self continueFindingMoreRecipes]; //(do this in background without locking screen)
[self loadRefreshButton];//At completion load this button (a blinking button) to refresh the cells with new results
}
A typical pattern you can use is something like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// perform data processing here (done in the background)
dispatch_async(dispatch_get_main_queue(), ^{
// update user interface here (done on the main thread)
});
});
You could do batch requests where you cache the next X amount of answers every time your UI got with in Y of the current end. Depending on what you are using a lot of databases have protocols that can help you batch cache easily. Good luck!
Use grand central dispatch. Create a new queue, dispatch it with a block and when you need to update, call dispatch get main queue. There is no way to pause this once the queue has been dispatched though. Maybe load recipes into intermediary then update as needed.
Look for some gcd tutorials there are a few decent ones there.
Would give you more code but I'm typing on iPhone.
It strikes me (especially since you are, by your own admission, "very new to programming") that it might not be prudent to pursue GCD to prefetch data in a background queue, make sure you coordinate the background queue's database operations with the foreground's database operations (possibly via something like FMDB's FMDatabaseQueue or something equivalent), gracefully handle pausing this operation as you go to other screens (as you indicated in your question) and making sure you don't cause retain cycles in the process.
To make matters worse, I gather that this is all in pursuit of a possibly questionable goal, if I understand you correctly, to retrieve 10,000 recipes in the background. Don't get me wrong. You certainly can design all of the background operations like we've outlined, but I think you should stop and ask yourself whether that's the right design for your business problem.
I might suggest an infinitely easier solution. Just load your tableview with the first x recipes, and as the user scrolls down, when you start to approach the end of the tableview, detect that fact and retrieve the next x records and add them to the tableview. But any user interface that is expecting the user to flip through 10,000 entries doesn't pass the smell test. When I think of a database with 10,000 entries, I think of something more like a imdb/wikipedia/google/facebook-like user interface rather than a contacts-style user interface.
Regardless, you almost certainly don't want your app just endlessly retrieving recipes in the background. You can solve your UI performance issue, but maybe replace it with memory management issues. And you're going to design a complicated system architecture when it's not entirely clear whether your recipe app requires that.

What's a good general approach for apps like pulse, youtube, NYTimes, etc? Use UIWebView or something else?

That may seem like a broad question, but I'm talking specifically here about apps that display a lot of content (image plus some text) pulled from the net in separate cells, rows, etc. And where each of those cells can get loaded asynchronously (independently from all the others).
So for example, for iOS, is it too inefficient to use uiwebview for each of these cells? It seems like this would be a simple approach, but I'm not sure about the performance. "Pulse" has a bunch of cells on the page at one time, and on iPad this could really get to a large number. Is it better to do this using lower level techniques, or is using UIWebView a decent choice?
Update:Clarification-->
Just to clarify, I understand that the typical approach is to use UITableView and then create table cells to hold and show the data (although the "Pulse" UI may be more involved). What I'm getting at here is if those UITableViewCells could hold UIWebViews? So instead of putting a UIImageView and a UITextView in each cell and so forth, if it makes sense to just put UIWebViews there instead, and give them URLs (could pass a param to indicated the row) to load as cellForRowAtIndexPath is called or something.
Well, if you talk about the YouTube app, its not UIWebview. The common and better approach is to use UITableViews along with the custom UITableCells. As far as asynchronous loading of images is the concern, we implement "Lazy Loading" mechanism. And in this way, we are able to keep the performance benefit of native UIView intact. Whereas, in UIWebView, you would need to design the iphone based webpages which will cost you learn about Dashcode tool, or may be someother opensource css library, etc.
In short, UITableView, with Custom UITableCells, along with Lazy Loading Mechanism (achieved by some Threading Techniques) will do all the Magic for you.
Hope this answer will give you some better idea....
Do not use UIWebViews with UITableViews like that. There are far too many moving parts for a UITableView!
I wrote the NYTimes iPhone app while I worked there. I only ever used a UIwebView to display article content. This is because NYT articles and log post layout is extremely complicated and impracticle to replicate programmatically. (I did override some of the CSS in app)
The UITableView is a super high performance implementation for butter smooth high speed scrolling. UIWebViews are the opposite of that: heavy, slow and memory intensive. In fact web views actually run internal virtual machines and threads (for JavaScript, rendering, etc).
Keep in mind that table view cells are recycled very aggressively. The instant a cell scrolls off screen bottom it will, in gneral, be immediately reconfigured for use as the new cell that scrolls in from the top.
With a UITableView, you need to create VERY highly optimized custom UITableViewCells. The data displayed in the cells should be cached aggressively by you so that you don't need to recompute it or perform intensive layout calculations. Uncacheable information such as remote images should be loaded asynchronously (which might mean they show up much later).
On caching:
The NYT apps have a high performance caching system for images. The trick is to request the imge data as soon as its "needed", but to also preempt downloading images which were needed a second ago, but no longer. You also don't want to cancel partially downloaded images because that wastes the already consumed bandwidth. Check out NSOperationQueue. It has most of the levers needed to build such a system.
Another note: consider building a web app if your project may also be accessed via web or an android app.

iPhone: Reading many images quickly

I've got an app I'm working on where we handle a LOT of images at once in a scrollview. (Here's how it looks, each blue block being in image on a scrollview expanding to the right: http://i.stack.imgur.com/o7lFx.png) So to be able to handle the large strain doing this puts on memory. So I've implemented a bunch of techniques such as reusing imageviews etc which have all worked quite successfully in keeping my memory usage down. Another thing I do is instead of keeping the actual image in memory (which I of course couldn't do for all of them because that would run out of memory very quickly) I only keep the image's filepath in memory and then read the image when the user scrolls to an area of the scroll view near that image. However, although this is memory efficient, it's causing a LOT of lag in the scrollview because of the fact that it has to constantly read images from the disk. I can't think of a good solution on how to fix this. Basically right now the app draws to the screen only the visible uiimageviews and while the user scrolls the app will look to see if it can dequeue another imageview so it doesn't have to allocate another one and at that point it reads the image into memory, but as I said it's causing the scrolling action to be very slow. Any ideas on a strategy to use to fix this? Does anyone know what the native photos app does to handle this kind of thing? Thanks so much!
I can suggest you a simple solution to balance both the memory and the computer processing. You only keep small images like thumbnails in memory and only keep about 20 of them. One project that I am doing, I keep 20 thumbnail images (100 x 100) recently accessed, which doesn't cost a lot of memory. I believe that it costs about 200 kb all the time but comparing to a general available memory. I think it is good enough.
It also depends on your use case : if user scroll really fast and you don't know when will they go. You can have even smaller images than the thumnail and when you show it on the UIImageView, you resize it to fit. When user stops scrolling for a while. You can start loading bigger images and then you have a nicer images. User may not even notice about the process
I don't think there is a solution that can be fast and using as less memory as possible. Because we have memory, maybe not big but have enough if we use it smartly.
Slow scrolling performance might mean that you're blocking the main thread while loading images. In that case, the scrolling animation won't continue until the images are loaded, which would indeed cause pretty choppy scrolling performance.
It would be better to lazily load requested images in the background, while the main thread continues to handle the scrolling animation. A library that provides this functionality (among other things) is the 'three20' library. See the Tidbits document, and scroll down to the bottom where the 'TTImageView' class is described.
I had a similar issue with a PDF viewer, The recommended way to do this is to have as low a res image as you can get away with and if you are allowing the user to blow the image up/zoom, then have two versions or three versions of that image increasing the res as you go.
Put as much code as you can get away with in the didDecelerate method (like loading in higher res images like vodkhang talks about), rather than processing loads in didScroll. Recycle Views out of scope as you have said. and beware of autoreleased Context based Image Creation functions.
Load images in on background threads intelligently (based on the scrollView Offset position and zoom level), and think about using CALayer/Tiled Layer drawing for larger images.
Three20 (an open source iOs lib) has a great Photo Viewer that can be subclassed, it has thumbnail navigation, large image paging, caching and gestures right out of the box.

Using CGLayer to Cache Cells in a UITableView

I am trying to improve the performance of scrolling in our app. I have followed all of the generally accepted advice (draw it yourself with CG, the cell is opaque, no subviews, etc.) but it still stutters sometimes when we have background CPU and network activity.
One solution stated here:
http://www.fieryrobot.com/blog/2008/10/08/more-glassy-scrolling-with-uitableview/
is to cache bitmap snapshots of the cells, which we tried. But the bitmaps were fuzzy and took up a ton of memory (~a few hundred kb each).
One suggestion in the comments of that link is to cache the cells using CGLayer or CALayer(?) because they go to the graphic card's memory. So a few questions,
1) Has any tried this? Sample code?
2) How much memory does the iphone/ipod touch graphics card have? Does this make any sense?
3) Any other suggestions for speeding things up?
More information
I used the CPU sampler (on the phone) and systematically eliminated things from the cell to figure out the problem. A few things:
1) It isn't the setup of the cell. If I remove just the drawing calls (drawinrect etc), but leave the setup, it is glassy.
2) It isn't the drawing of the smallest image (25x25 png), if I put that in it is fine.
3) If I add the second or third image (the first is a big background 320x1004kB, the other is a button image 61x35 4kB) it stutters. I am grabbing both UIImages in a class method, so it is cached.
4) The text is also a problem. It looks like by drawRect spends 75% of its time in the three NSString drawInRect methods I am using. Like:
[mytext drawInRect:drawrect withFont:myFont lineBreakMode:UILineBreakModeTailTruncation];
Those calls seem to go through webcore, perhaps that causes some of the stutters? I need to be able to format two lines of text, and one small paragraph of text. I need the ability to truncate the text with ellipses. Can I perform these out of the critical path and cache them? May I can do that part with a layer?
CGLayers are not cached on the GPU, they are used during the process of drawing Core Graphics elements to a context. CALayers do have their visual contents cached on the GPU. This can "hide" some of your memory usage, but you're still going to run into memory problems if you hold on to a lot of CALayers.
Honestly, if you've followed the table view best practices, as described by Loren Brichter and others, of drawing all of your content via Core Graphics in one layer, making your cell opaque, and obeying the cell reuse mechanism of the table view, there isn't much more you can do. Overloading the CPU on the iPhone will cause stuttering of your scrolling, no matter how optimized you can make it. The inertial scrolling animation does require some CPU power to run.
One last thing to make sure of is that the background CPU and network processes you refer to really are running on a background thread. Anything running in the main thread will cause your interaction methods to pause while that task is processing, potentially adding to the choppiness of your scrolling.
Before you go too deep into the optimisation, are you using the cell reuse mechanism ([tableView dequeueReusableCellWithIdentifier:]) for creating UITableViewCells in your delegate's tableView:cellForRowAtIndexPath: ?
Also, have you run the code in the Simulator using Instruments' Activity Monitor? Set the interval to 1 ms (I found the default - 10 ms - too big for this) and see where most of your time is spent when doing the scrolling.

Do I have to run the delegate of an UIScrollView in another thread to prevent performance problems during scrolling?

I'm going to do some sophisticated things in the delegate's methods during scrolling. I'll also implement the dynamic preloading of contents, so that it's theoretically possible to scroll through a few hundret thousand images. But I fear, that every time I do the preloading action for the next big chunk in scrolling direction, the delegate will wait for the data source to deliver the data, and the whole scroll view will be freezed for that moment.
The problem is, that I need always access to the subviews of the scroll view. I'm new to the platform and I don't know if I would still have access when I open up another thread for that preloading actions? Or would the scrollview not wait for the delegate to get things done?
I'm in the planing phase, so haven't implementet much jet.
You can only use UI classes from the main thread. So what you should do is to compute as much as possible in a background thread (I believe you can load your images in a background thread too), and then use performSelectorOnMainThread:withObject:waitUntilDone: to manipulate UI classes on the main thread.
See How do I update the UI in the middle of this thread? for the another instance of your question
I don't have a specific answer, to your specific question. I just want to make sure you are aware of the ~25MB limit of RAM for your app. I can't give links on this, because even though Apple knows the limit, they aren't telling.