Suppose you have several animations that we want to achieve with a sequence of PNG in a UIImageView.
If we have many images in the animation we have a delay from the moment we send the message [myImgView startAnimation] this becausethe loading of all images in memory.
I noticed that the loading is lazy: as long as the startAnimation message is not sent images are not loaded into memory.
To avoid the problem of delay i load all the animation in the app delegate, attach as subview and than animate once. I want to understand what is the best solution? And if my actual solution have a draback?
You're right about lazy loading. I've never been able to determine if its actually lazy spooling from disk or lazy decompression but in either case, a UIImage (and, underneath, a CGImage) is not necessarily fully processed and ready to draw until it is actually used. I assume that it may conceivably become unready again in the future, depending on exactly how Apple handle memory warnings internally.
If you wanted to be really keen, I guess the best solution would be to use CoreGraphics to load and decompress the images in the background, pausing to finish loading upon startAnimation only if loading hasn't already finished. You can't achieve that directly with UIKit objects since they're not callable anywhere but on the main thread. You'd need to get a CGImage, draw to a bitmap context then create an image from the bitmap context and post that onto the main thread to be wrapped into a UIImage. And it'd probably be smart to use an NSOperationQueue to marshall the complete list of operations.
Supposing you don't mind the additional startup cost of blocking while you load all the PNGs, and are dealing with memory warnings correctly, there shouldn't be any problems with your current approach and I can't think of a better solution while remaining in high level Objective-C stuff.
Related
I want to know, what is the best way for lazy loading. For me, most of the applications i have used parsing and get the url from the server and put the image into the table view. So i have implemented lazy loading for improving the performance of the application. Now i want to know the best method for lazy loading images. Because i have used lazy loading into the four ways,
Lazy loading images from Apple developer.com
Implemented Asynchronous method for improving the lazy loading
Used separate main thread for handling the image downloaded.
Have used ECOImageLoadingDemo application for lazy loading.
But i have used the above four methods to achieve the lazy loading. But i want to know what's the best method for lazy loading. Which one is best for performance wise and memory wise is suitable for that?
Thanks in Advance.
Regards,
Pugal
From my experience, performance-wise and memory-wise solutions are on the opposite ends of a slider. You can move around with your solution somewhere between these two, but with the disadvantage that having the best solution performance-wise, usually means a worse solution memory-wise and vice-versa. I hope I explained this clear enough :)
Here's how I handle the problem of lazy loading images:
In my application I created ONE entity which I called GlobalImageProvider. All requests for images go through this entity. This way I have control on how many threads I use to download and I can implement a caching system (memory + local disk), all of these completely transparent to the application and with full control.
By controlling the size of the cache, I can control how quick the application feels. Performance wise, nothing compares with having an UIImage already created in memory.
Memory-wise, you can even choose to disable the cache.
Even more, I can even change the number of threads dynamically while the application is running depending on the quality of the network I have.
To make the online requests, I'm using NSURLConnection but I plan on switching to something else since I've read that it leaks memory.
On the view&controllers side, I have a AsyncImageView which is just a UIImageView that knows how to work with the GlobalImageProvider. It knows to display an activity indicator while loading and can handle the response from GlobalImageProvider.
If you know the URL of the image you want, all you need to do is add a AsyncImageView to your screen and make a request to the GlobalImageProvider with the AsyncImageView as the "handler" for that image.
If you don't like mixing data with the image views, you can add a ViewController between the GlobalImageProvider and AsyncImageView. He gets the image response and puts it in the ImageView.
That's about it, hope it helps you a bit.
You can take a look at this excellent tutorial from Peepcode in the section Bonus: KVO for Cell Images
http://peepcode.com/products/iphone-view-controllers-part-ii
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.
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.
My app is doing some pretty but heavy weight core animations during scrolling. Sometimes it crashes due to bad performance. So I need some way to find out if there is enough capability to make the animations, and if not, I just leave them away. Best way would be if I could ask the system how busy it is.
UPDATE: I mean especially Core Animation.
By animation, do you mean frames that play after one another (like an animated GIF) or some CoreAnimation (OpenGL) effect that is moving polygons with mapped textures around?
If it's the former, I'd really consider some way of optimizing the animation or eliminating it in all cases.
If it's the latter, I'd do some deeper digging into the source of the problem. Core Animation under normal circumstances will drop frames in order to keep from getting into situations like this in the first place.
In either case case you might consider loading the texture assets a little earlier. I have had some trouble in my apps with animation methods that take a UIImage parameter when I created the UIImage in the function call. Preloading the asset a little earlier in my code took care of the problem nicely.
As an example:
BAD
[[UIImage imageNamed:#"checkmark.png"] drawAtPoint:p];
BETTER
//declared at top of class
static UIImage *checkmark = nil;
in init:
checkmark = [UIImage imageNamed:#"checkmark.png"];
in drawRect:
[checkmark drawAtPoint:p];
You would need to adapt this technique to your particular situation. In my case, checkmark is used often, and it quite small, I don't mind dedicating the memory to it permanently.
I wonder if your crashes could be fixed by making sure the assets were ready to be used by the application.
I wouldn't do that. If your app crashes, it's to heavy. You could run your app with some instruments to see where your bottlenecks are.
So, without trying to sound too harsh, your best way is to rewrite some parts in order to make you app run on an iPhone at all times.
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.