Advice on using sandbox vs. caching for UITableView async image download - iphone

Apple just released some sample code on lazy loading images in a UITableView a week ago. I checked it out and implemented it into my own UITableView (which is a drawRect one for fast scrolling), to see if there was a difference from what I was already doing.
After implementing I am not sure what is best; the new code or what I already had. I am not seeing much of a speed improvement on my 3GS.
"Sandbox" method: Load images lazily, then save to local tmp folder in the sandbox. Each time the cell is displayed it looks for whether an image with that filename is already located in the sandbox folder. If it is, it retrieves the image and displays it, if not it continues with the download, saves it locally and then displays it. The benefit with this is that the images won't be blank the second time you open the app. They will already be downloaded and ready for displaying.
Caching method: This also loads the images lazily, however, now I include a UIImage on each object in the array that's displayed in the tableview. Instead of saving the image locally, I now download the image and put it into the array for the object. Now, instead of checking for the filename every single time, it jut check whether the UIImage != nil and uses the cached image (or downloads if nil).
A small difference is also that the caching code resizes the image before caching it to the exact size of what is displayed in the cell, whereas the image used in the sandbox code example is actually a bit larger than what it needs to display, which means it has to resize on the fly when scrolling as well. I read months ago that this could be a bit expensive to do, and I am also not sure whether it makes much of a difference in terms of then using a cached image instead of the sandbox-stored image and therefore more CPU intensive anyway (compared to what you save from caching with the caching code above).
I guess my question would be whether I should even bother with the caching code? Again, the new code won't immediately load images on a new launch, whereas the old code actually does because it's already in the sandbox. Since I am not reusing images, I have a lot of images to load (from the sandbox or cache) so I am not noticing a huge difference in speed. In fact, on my 3GS it's almost impossible to tell, in my opinion. The scrolling is not silky smooth, and I assume this is due to the large amount of images that I cannot reuse (different image for each cell). I am also wondering whether the sandbox method would get slower once there's 1000+ images in the folder, for example, eventually having it look through many more images than just 100 or so.
I hope I am making sense. I wanted to be pretty thorough with the details, and I am happy to give more details if needed.
Thanks!

If you have code that already works, and there's not a pressing problem, then don't change it.
If your scrolling actually is too slow, then perhaps you could use a mixture of ideas, and try to get the UIImage, and if it's not there, load it from the sandbox, and if it's not there, then download it.

The only good way to tell if there is any discernible difference in performance is to use profiling tools like Instruments (for measuring things like display framerate for the two techniques) or Shark (to determine hotspots in your code). There could be small differences in your exact implementation that could potentially cause significant differences between any general answer we could give and the actual performance you see in your application.
The thing that primarily concerns me with the "sandbox" method is not performance but disk space usage. Users won't appreciate you filling up their iPhone or iPod Touch with unnecessary files, especially if all the images aren't consistently used or if the set of used images changes often. Without knowing more about your application its impossible to guess how often these cached images would be loaded.
If you're testing locally on your own device, you might be on Wifi network. My recommendation would be to turn Wifi off for part of your testing to see how the two approaches perform when you have to fetch all the images over the cellular network. I would also recommend trying to find an older device (iPhone 3G or worse) because the 3GS does in fact hide potential performance issues that could be annoying for users on older devices.
I have personally used the LazyTableImages technique in my apps many times (provided it hasn't changed drastically between WWDC09 and the recent 'release') and find it to be just what I need. Caching images on disk wouldn't be an option in my case, however, and you shouldn't take my anecdote too strongly into account - profile your own code and use the results it shows.
Edit: The obvious answer is that accessing an in-memory cache is going to be faster than accessing the filesystem, but of course the final word on that is left up to profiling. If the images are already in memory, they don't need to be read from flash and parsed by UIImage. The traditional tradeoff comes into play here though - in-memory caching vs. disk space.
While it may be faster for you to store your images in-memory, you need to be very sure that you correctly handle memory warnings in your application (as you should be doing anyway!). Otherwise long period of use will lead to many, many images in your in-memory cache and trigger memory warnings and if your application is not built to handle these, at best your application will be killed by the OS due to lack of memory resources.

There are pros and cons in both approaches that you present - I suggest using elements of both in your app.
It's better to keep your images in memory and save them later (perhaps when your app quits). If you have a lot of images, it might be faster to use Core Data to save them, than as regular files.
It's also better to avoid doing any resizing on the fly, i.e. in your tableView:cellForRowAtIndexPath: or tableView:willDisplayCell:forRowAtIndexPath: methods or in any method that has to do with drawing your cells' content view. If you can, ask the image provider (content management?) to supply images at the size that your table view displays.

Related

How to create offline apps with Service Worker and srcset?

I would like my app (a static web site) to run offline using a Service Worker. I can't see a way to do this without caching all the images from the srcset attribute. I can see how client hints would solve the problem but that apart is there a solution that would work without involving the server doing anything but serve requested files?
I can see perhaps how a Service Worker could calculate the image to request given the information in the img tag and a naming convention for images...
Has anyone tackled this problem, or thought about it at all?
For full srcset functionality you would have to cache all resolutions indeed.
While the screen density may seem to be a fixed property of a device, it actually is dynamic, e.g. a smartphone can cast/airplay to a TV screen. On desktops with multiple displays (e.g. Retina MacBook with an external display) screen resolution may change when the browser window is moved around. All these changes may happen offline long after you've done caching, so you can't know for sure which resolutions will be picked.
A simple solution is to always use 2x images for everything. Higher DPI makes image distortions less noticeable, so you can compress them more heavily to offset the cost of higher resolution.
Another solution is to catch loading errors on images and replace srcset with image URL that you know is cached. BTW: you may need to add onerror="…" in the markup, because the error may fire before any other scripts had chance to run on the page yet, or before adding error handlers programmatically check all image elements for img.complete && !img.naturalWidth to detect missed error events.

What are Recommended Ways to Optimize Memory and Usability Speed of custom UITableViewCells with multiple UIImages?

I am using a custom UITableViewCell with 3 UIImages in a UITableView with 50-100 rows. Its similar to a UITableViewCell the Facebook iPhone app uses for its news feed view.
The application has 4 similar UITableViews which may be open at the same time via a UITabConroller.
The images are lazy loaded, there is a cache on disk so that no images are loaded twice from the server and there is also a NSMutableDictionary for images allowing in-memory reuse of the same image eg: a users profile picture appears multiple times
This setup is extremely fast but takes a lot of memory even after using the NSMutableDictionary for image reuse.
I tried a variation without the NSMutableDictionary where images are either loaded from the server or pulled from the disk cache every time cellForRowAtIndexPath is called. This setup is extremely memory efficient but causes a noticeable lag in the UITableView scrolling.
A mid-way approach is to free the NSMutableDictionary for images when a low memory warning is received.
Will really appreciate recommendations to optimize memory usage and speed in this scenario and or an insight into how the Facebook iphone app or three20 execute this conceptually.
I have an app that is very similar to yours in many respects (uses Three20, has several tabs across the bottom, each tab can have a table, each cell can have one or two images); and the approach I'm taking is the one you mentioned near the end of your post:
A mid-way approach is to free the NSMutableDictionary for images when a low memory warning is received.
Personally, I quite like iOS's approach to memory management, of warning me when memory is getting tight. The Mac/PC approach of "just use all the memory you want, we'll swap it out to disk if memory gets tight" has the disadvantage that even though the OS is the only one who really knows how much pressure there is on memory, it isn't telling you. I think what every polite app would really like to say (if apps could talk) is, "I'd be happy to use as much memory as you'll give me, but I don't want to be a bother, I don't want to slow down any other apps, so if you could please give me a hint as to how much memory I can use without causing problems, I would appreciate it."
Well that's what iOS's memory warnings give you, in my opinion. So, just keep as many images cached in memory as you want; and when you get a memory warning, empty the in-memory cache. To me it's really the best of both worlds.
Also, you should definitely take a look at Three20's TTURLCache, although I can't tell you a lot about it because I haven't dug into it very much. What I do know is:
If you retrieve your messages via TTURLImageResponse, it will automatically cache them in TTURLCache's image cache.
You can also store and load your own images (and other data) in the TTURLCache.
Three20 seems to take an approach similar to what I am talking about. Take a look at this code from Three20Network/Sources/TTURLCache.m (the NO argument means don't remove from disk, only remove from memory):
- (void)didReceiveMemoryWarning:(void*)object {
// Empty the memory cache when memory is low
[self removeAll:NO];
}
In addition, that class also allows you to set a maximum size for the in-memory cache, but by default there is no maximum size.
you would purge images when they go offscreen, then read the images from the locale cache on demand from a secondary worker thread when needed. since one can zip through tables, add support for read cancellation (esp. for requests which come off the server). NSOperation is a good api for this.
if you know your table's small, then you could opt to avoid purging in such cases.
also, rescaling the image to the size you'll display it as is often a good idea (depending on how far you want to take an optimization). assuming the source is larger than the displayed size: this will reduce memory requirements, drawing speed, disk space, and disk read times.
you can also read three20's sources to see what they have done.

iphone memory issue

I have an iPhone application that will save number of images in it.
I used SQLite in-order to save the images into the application.
There were lot of memory issues after i saved more than 20 images.
Do any one know how many images users can save in their app database?.
if it depends on iphone memory, how can we get that max limit?.
One more thing:
I have removed the database and used the file system to store the images into application.
but same problem replicated.
Can any one suggest me on this.
I owe a lot for your great help.
Thanks in advance.
I believe storing the image in the file system is a much better idea, could you provide us with more information, like image size, and also some of your saving code?
When you say 'memory issue', do you mean leaks causing out of memory exceptions or are you sure your database has filled the device's disk completely. To know how much SQLite can store read the discussion on this question.
On the other hand, if you are having out of memory exceptions (didReceiveMemoryWarning) you need to tune your code. Specially, when working with many images, just avoiding the use of 'imageNamed' factory method does the job. This is because it creates an autorelease object which remains longer in the memory. Instead create UIImages using the 'initWithContentsOfFile' to create the image and release it immediately after it is used. If you still face the memory issue, you probably have some leaks and need to post some code for people to answer more correctly.

iPhone and Vertex Buffer Objects

I've just started playing around with opengl es on the iphone the past couple of weeks and i'm looking at refactoring some of my code to use Vertex Buffer Objects(VBO). Before I do though I would like to make sure it'll be worth it. The problem is that afaik the only reason you create VBO's is to shift a chunk of data onto the graphics card so that it doesn't need to be retrieved from system ram when it's used. The iPhone however does not have any dedicated ram that I'm aware of so i'm struggling to see why I would benefit at all from using VBO's. I have seen talk around the internet with conflicting opinions and apple certainly want dev's to use it so there's probably still a reason to use them but just wanted to see if anyone on SO had an opinion to add.
I saw no performance improvement on an iPhone 3G. I moved a bunch of stuff to VBOs, but eventually backed it out as it made it more difficult for me to pursue other performance gains. It's not the quick 25% performance increase that I was hoping for.
I've read somewhere that it can make a difference on the newer hardware (3GS), but I don't have references to back that up.
It depends. (sorry).
Rob didn't see an improvement for his setup, but here is an interesting post that did see a large improvement.
The main reason to existence of VBO's is the presence of static data on 3D models. The first bottleneck you encounter is the slowness of copying data to video memory (by using the unavailable glBegin/glEnd block or glVertexPointer, glBufferData and friends).
Let's imagine the old "flying toaster" screensaver. All toasts are static (changing only the position) - why waste resources copying them every frame from CPU's memory to GPU's? Copy it once with buffers and draw it with a single command. And, depending on how you do animations, even the animated toasters can be described in a static fashion.
My first 2D game I started without VBOs. When I changed to VBOs, no difference (like Rob). But, when I refactored to use more static buffers, FPS gone from 20 to 40. Since my goal was to reach 30, I was satisfied. I had some ideas to refactor even more, leaving everything static, but I don't have time now (game is on review, next one to come).

Best approach to debugging applicationDidReceiveMemoryWarning on iPhone?

Need advice on how to debug this. I'm new with limited environments and have no previous embedded or smart phone programming experience so I could use some clues.
Already aware of:
Instruments, Clanger Static Analysis, manual code review, etc. Instruments seems to be very helpful in general but quite time consuming and freezes up a lot of the time! Clanger has also helped me a lot as well. It seems like I'm just consuming too much memory in general and I'm wondering what a good strategy is. Do I release some top-level objects? Is there a 'preferred strategy'?
Just wondering if anyone has tackled this successfully and if they have any other suggestions? Thanks all.
There are a lot of good articles for memory management in an iPhone app. Here are some useful links.
http://iosdevelopertips.com/objective-c/memory-management.html
http://kosmaczewski.net/2009/01/28/10-iphone-memory-management-tips/
https://cocoa-touch.blogspot.com/2008/09/memory-management-on-iphone.html
Things you should in general take care of
Release any variables which you do not need
Always handle didReceiveMemoryWarning and release any variables not in use
Stop any memory-heavy processes in applicationDidReceiveMemoryWarning like audio/video playing, UIImagePickerController etc
EDIT
This does not apply any more. imageNamed: had caching issues prior to 3.x OS versions. The issue does not exist any more and you should use imageNamed: (makes implementing retina display easier)
Do NOT use imageNamed: to create UIImage objects.
Basically you're receiving this warning because (unsurprisingly) the iPhone is dangerously low on memory. This can generally be for one of two reasons;
You have a memory leak.
You are allocating far too many objects and need to revisit your design.
For the first one you should run instruments and examine your memory allocations. This can really slow down your app (and requires additional memory) so try testing areas of your app one at a time. E.g. if you have multiple views switch between them a couple of times.
For the second you will have to examine things you are doing that could result in large memory allocations. For example if you're writing a Flickr browser you might need to cut down the number of images you have loaded at anyone time, or free up some unused ones when you receive this warning.
These are about the only general rules I can suggest without knowing more about your app.
Unfortunately there's no real way (that I know of) to get figures for current memory allocation from the iPhone OS. This makes it really difficult to isolate the areas of your application that are inadvertently memory hungry.