My UIButton has it's background image set like this:
[myImageButton setBackgroundImage:[UIImage imageNamed:myImageName] forState:UIControlStateNormal];
myImageButton is a retained property of the class, and is setup with IB. No where else is it accessed in the app.
myImageName is simply an NSString with a filename like #"myImage_number_1.png"
I am loading large images, 1024 x 1024 in size. When the view is shown, it changes the image with the above statement, then available memory decreases.
After I see the view about 7-9 different times, the app crashes with a memory warning.
I thought the method would free up the loaded image.
The view itself is only instantiated and allocated one time, so it's not in the retain/release cycle if the view controller.
Is there something about this setBackgroundImage I don't know that causes it to not release memory?
Ah, found it. Every time imageNamed is used to load an image, it caches the image in memory. I switched to imageWithContentsOfFile - it doesn't cache images.
To future coders, #just_another_coder answer is correct, but there's something you all should know.
[UIImage imageNamed:myImageName] loads the image in a special system cache, and then future calls with that image path will return the image in the cache instead of reloading it from disk.
[UIImage imageWithContentsOfFile]simply loads the image at the path you specify, but does no caching. Multiple calls to imageWithContentsOfFile for the same image will result in multiple copies in memory.
So you should think about which one you'd rather, and if you use imagewithcontentsoffile you should remember to nil out that button otherwise you'll be doomed to an ever growing app (memory usage wise)
Related
The first view inside my application will only need to be shown once.
I am using the following inside a custom segue, to get it off the Navigation Controllers Stack and transition to the new one:
- (void)perform {
UINavigationController *nav = [self.sourceViewController navigationController];
NSArray *viewControllers = [NSArray arrayWithObject:self.destinationViewController];
[nav setViewControllers:viewControllers animated:YES];
}
While I was able to confirm, that the dealloc method gets called, the memory usage doesn't go down. I am 100% certain, that the memory was allocated by the ViewController, that I would expect to be released, since it contains a pretty big image (UIImageView) and the other view controller is tiny (memory-wise).
I am also sure, that I am not holding any references to the contained elements anywhere else.
Could it be that UIImageView keeps the image in memory, in case it would be needed again?
Is what I am doing even a good way to go? (I was inspired by this)
As a result of a quick test, I infer that image views that have images set in Interface Builder appear to be doing some caching. I assume it's the same caching mechanism used by imageNamed, whose documentation says:
If you have an image file that will only be displayed once and wish to ensure that it does not get added to the system’s cache, you should instead create your image using imageWithContentsOfFile:. This will keep your single-use image out of the system image cache, potentially improving the memory use characteristics of your app.
I don't know of any way to simulate the memory pressure that will cause this cache to be purged (other than obviously doing sufficient allocations to result in actual memory pressure). The various caches don't generally respond to the simulator's "Simulate Memory Warning" strangely enough, even though they do respond to true memory pressure. (Besides, you're testing on physical devices because of location services.)
But you can test to see if this indeed the issue by not setting the image in Interface Builder, but rather do it programmatically (and do it without using imageNamed), e.g.:
NSString *path = [[NSBundle mainBundle] pathForResource:#"imagename" ofType:#"png"];
self.imageView.image = [UIImage imageWithContentsOfFile:path];
In my test, when I dismiss a scene in which the image was set in Interface Builder, I do not recover as much memory as I do when I use the above code instead.
I have a UIImage that I use throughout my app as a background image for grouped UITableViews.
I thought that for efficiency I would alloc and init the UIView with my UIImage in my appDelegate and then access throughout my app. That way I would only allocate that imageView once and if I was drilling into a nav stack with multiple tableviews with this image I wouldn't need to worry about releasing and restoring the image as I descend and ascend or incur overhead at each step.
As soon as I tried this I noticed that it seems that the UITableView class is releasing the my shared image down to 0 and it therefore is going away. Makes perfect sense but I would need to prevent the image from ever hitting a 0 retain count for this to work.
Is this a totally goofy approach?
If it is not what's the best way to retain my shared ImageView? I know I could call retain when I setup each tableview's backgroundimage but I was wondering if there is a way to set the retain count of the shared UIImageView to NSUIntegerMax in my appDelegate. I've setup singleton classes before but in this case I'm trying to have a single property that is never released rather than creating a UIImageView singleton subclass.
Sorry if that's a little muddled and thanks for any pointers.
I would not worry so much as + (UIImage *)imageNamed:(NSString *)name are cached.
From the spec:
This method looks in the system caches for an image object with the specified name and returns that object if it exists. If a matching image object is not already in the cache, this method loads the image data from the specified file, caches it, and then returns the resulting object.
I'm building a UITableView similar to iPod.app's album browsing view:
I'm importing all the artists and album artworks from the iPod library on first launch. Saving everything to CoreData and getting it back into an NSFetchedResultsController. I'm reusing cell identifiers and in my cellForRowAtIndexPath: method I have this code:
Artist *artist = [fetchedResultsController objectAtIndexPath:indexPath];
NSString *identifier = #"bigCell";
SWArtistViewCell *cell = (SWArtistViewCell*)[tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil)
cell = [[[SWArtistViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
cell.artistName = artist.artist_name;
cell.artworkImage = [UIImage imageWithData:artist.image];
[cell setNeedsDisplay];
return cell;
My SWArtistViewCell cell implements the drawRect: method to draw both the string and image:
[artworkImage drawInRect:CGRectMake(0,1,44,44)]
[artistName drawAtPoint:CGPointMake(54, 13) forWidth:200 withFont:[UIFont boldSystemFontOfSize:20] lineBreakMode:UILineBreakModeClip];
Scrolling is still choppy and I just can't figure out why. Apps like iPod and Twitter have butter smooth scrolling and yet they both draw some small image in the cell as I do.
All my views are opaque. What am I missing?
EDIT: here's what Shark says:
I'm not familiar with Shark. Any pointer as of what are these symbols related to? When I look at the trace of these, they all point to my drawRect: method, specifically the UIImage drawing.
Would it point to something else if the chokehold was the file reading? Is it definitely the drawing?
EDIT: retaining the image
I've done as pothibo suggested and added an artworkImage method to my Artist class that retains the image created with imageWithData:
- (UIImage*)artworkImage {
if(artworkImage == nil)
artworkImage = [[UIImage imageWithData:self.image] retain];
return artworkImage;
}
So now I can directly set the retained image to my TableViewCell as follow:
cell.artworkImage = artist.artworkImage;
I also set my setNeedsDisplay inside the setArtworkImage: method of my tableViewCell class. Scrolling is still laggy and Shark shows exactly the same results.
Your profiling data strongly suggests that the bottleneck is in the unpacking of your PNG images. My guess is that 58.5 % of your presented CPU time is spent unpacking PNG data (i.e. if the memcpy call is also included in the loading). Probably even more of the time is spent there, but hard to say without more data. My suggestions:
As stated before, keep loaded images in UIImage, not in NSData. This way you won't have to PNG-unpack every time you display an image.
Put the loading of your images in a worker thread, to not affect the responsiveness of the main thread (as much). Creating a worker is real easy:
[self performSelectorOnMainThread:#selector(preloadThreadEntry:) withObject:nil waitUntilDone:NO];
Preload images far ahead, like 100 rows or more (like 70 in the direction you're scrolling, keep 30 in the opposite direction). If all your images need to be 88x88 pixels on retina, 100 images would require no more than two MB.
When you profile more the calls to stuff named "png", "gz", "inflate" and so forth might not go way down your list, but they will certainly not affect the feeling of the application in such a bad way.
Only if you still have performance problems after this, I would recommend you look into scaling, and for instance loading "...#2x.png" images for retina. Good luck!
[UIImage imageWithData:] doesn't cache.
This means that CoreGraphic uncompress and process your image every single time you pass in that dataSource method.
I would change your artist's object to hold on a UIImage instead of NSData. You can always flush the image on memoryWarning if you get them a lot.
Also, I would not recommend using setNeedsDisplay inside the dataSource call, I would use that from within your cell.
SetNeedsDisplay is not a direct call to drawRect:
It only tells the os to draw the UIVIew again at the end of the runloop. You can call setNeedsDisplay 100 times in the same runloop and the OS will only call your drawRect method once.
If the delay's happening in your -drawRect, then you might want to take a look at this article: Tweetie's developer explains pretty thoroughly the method he used to get that smooth scrolling you're after. This has become a bit easier since then, though: CALayer has a shouldRasterize property that basically flattens its sublayers into a bitmap, which can then—as long as nothing changes inside the layer—give you much better performance when animating the layer around, as UITableView does when you scroll it. In this case, you'd probably apply that property to your individual UITableViewCells' layers.
My guess is that the delay is from storing images in Core Data. Core Data is usually not a good way to store large blobs of data.
A better solution would be to store the images as individual files on disk, using an album id to identify each image. Then you would setup an in memory cache to store the images in RAM for fast loading into your UIImageViews. The loading of the images from disk to RAM would ideally need to be done on a background thread (e.g. try performSelectorOnBackgroundThread) so that the I/O doesn't bog down the main thread (which will impact on your scrolling performance).
Addendum 1
If you're only calling -drawRect: once per cell load (as you should be), then the problem might be the scaling of the images. Scaling an image by drawing it in code using drawInRect will use CPU time, so an alternative approach is to scale the images as you receive them from the iPod library (if you need them in multiple sizes, save a version in each size you require). You may need to do this on a background thread when you import the data to avoid blocking the main (UI) thread.
One alternative thing to consider is that UIImageView may do it's scaling using Core Animation which would mean it is hardware accelerated (I'm not sure if this is actually the case, I'm just guessing). Switching to a UIImageView for the image would therefore get rid of the CPU burden of image scaling. You would have a slight increase in compositing overhead, but it might be the easiest way to get closer to "optimum" scrolling performance.
At this point your best bet is to use Instruments (previously Shark) to try and find bottlenecks in your code.
Due to the lazy loading nature of view controllers in iPhone, my app is a bit slow in response the first time when you go to the various screens, which has a rich graphics elements (custom graphics assets) in it. Will preloading the app images ahead of time, that is, by calling [UIImage imageNamed: ...] ahead of time so the next time it's called a cache version is used instead.
Will this work? Any other tips that can speed up the loading of view controllers where rich UI is involved (i.e. lots of custom graphic assets used)?
Sending +[UIImage imageNamed:] does cache the image you're trying to load, so sending this message ahead of time should speed up subsequent image grabs (assuming they're in the cache) according to the docs. imageNamed: documentation is here.
You can also load the view controllers ahead of time and then call [viewController view] to force the view to load.
I don't think there's a guarantee that image or nib loading is safe to call from another thread. Image-loading might be safe if they've made the image cache thread-safe (NSMutableDictionary is thread-safe IIRC), nib loading is less so because init/viewDidLoad/etc might expect to run in the main thread.
If you do pre-load things in the main thread, then it'll block the UI. You can mitigate this e.g. by using performSelector:withObject:afterDelay: and a small delay (0.01? 0.1?); note that there's no guarantee. On OS 4, you can load each image in an operation on [NSOperationQueue mainQueue]; setting the operation priority to something small (0 or 0.1?) should ensure that the UI gets priority.
Also note that any pre-loading is a bit pointless if you use so much memory that you get a memory warning.
I have a UIImage that I'm instantiating using imageWithData: (the data is loaded from the bundle using [NSData dataWithContentsOfFile:]).
I'm then drawing the image like so:
NSData *imageData = [NSData dataWithContentsOfFile:fileLocation];
UIImage *myImage = [UIImage imageWithData:imageData];
//These lines are superfluous from what I can tell, replacing with
//UIImage *myImage = [UIImage imagedNamed:imageName]; very soon.
[myImage drawAtPoint:CGPointMake(0,0)];
//myImage will be released at the end of the run loop
My question is this: The UIImage created is autoreleased. What happens in terms of memory when the UIImage is drawn to the view and then the UIImage is deallocated out of existence. Obviously, visually, the image is still there as it has been drawn to a context.
Does the memory usage double if the UIImage is valid AND has been drawn to the view, and then return to the same amount as if only one UIImage existed after the UIImage has been deallocated?
Now going down another route.
If I used [UIImage imageNamed:] to instantiate the image, then the UIImage class has its own image cache of sorts and will only ever hold one true instance of a particular image (regardless of how many UIImage instances are created to represent that one image).
My other question is: What happens to the image in the cache if I draw it to a context and then the UIimage is released (via autorelease at the end of the run loop)? Does the image stay in the cache an consume memory? Is it removed because no other UIImage instances are using it?
Any help would be great, thanks!
I really recommend starting with simple code here and then optimizing where you are actually having trouble. The kind of space optimizations you're describing are likely to create serious time problems (recreating the UIImage in -drawRect: for instance is very expensive). That said, let's look through the various questions:
First, as I said, be careful with doing expensive work in -drawRect:. You have little control over how often or when it gets called, and any expensive work here (like creating a new UIImage, particularly if you have to read it from disk) can seriously impact UI performance.
I'm assuming that there's a lot more to your -drawRect: then this, correct? UIImageView is optimized for what you've done here, both in speed and in memory. But if your view is much more complicated, then drawing the image yourself is better than creating lots of sub-views.
As has been noted, when you call -drawAtPoint:, the copy you make is a bitmap representation (blended with any other drawing that is done). It's unrelated to the original UIImage in terms of memory usage. You cannot trade one for the other. The memory required for the bitmap representation is a function of the view size and bit depth, and you're not going to be able to change it. It doesn't care if the UIImage exists after it's been drawn.
-imageNamed: does do caching for you and is generally a good choice. Note that it doesn't clear its cache in low-memory situations. The UIImages themselves transparently dump their underlying data if they were loaded from a file (and they dump extra representations in any case). The UIImage reference includes information about how this is done.
If you're very concerned about performance around these images (space or time), and you don't need the functionality of UIImage, you should be working with CGImages. They are not as flexible as UIImages, and they're more complex to code, but they are more efficient. That said, UIImages are good for most purposes.
At any point, when you "draw to context", the context does not hold a reference to the image. Instead, drawing will place the actual bits of pixels on the context, so it doesn't need the reference to UIImage (or anything drawn to it - NSString, NSPath etc).
Regarding imageNamed:, it will never be really released - what you get is a refernece that you don't (and shouldn't) release, but the cached image might still be there.
It really depends on where you're creating the UIImage instance itself. If this is a UIView subclass, are you creating the UIImage in -drawRect: or -initWithFrame: or somewhere else? If you load the image once (in your init or with a setter which retains, etc.) then there shouldn't be a problem. However if you're creating the image over and over again in -drawRect: then at the very least you will have a performance problem.