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.
Related
My problem is when I use the camera UIImagePicker and the phone is low on memory. It throws a memory warning and unloads the view and then reloads.
I know this is a very common problem and many questions are already in SO. I'm not going into this.
- (void)viewDidLoad
{
[super viewDidLoad];
MyPersonClass *persons = [[MyPersonClass alloc] init];
persons.images = [NSMutableDictionary dictionaryWithCapacity:4];
....
...
}
My issue is the my view controller has four buttons and UIImageViews and when I tap each, they open the camera and image clicked is shown back in the UIImageViews and these images are also stored in the persons.images NSMutable Dictionary.
Sometimes it throws the popular error of memory warning and Unloads the view and this removes all the images in the UIImageView which were taken before the memory warning and I lose everything in persons.iamges I just want to be able to retrieve this data back. I don't know where to store it (I don't want to use CoreData for this).
You can maintain instance variables for storing image when viewDidUnload gets called. set a flag in viewDidUnload and then store all the images of your Imageviews. And in viewDidLoad check the flag that if viewDidUnload was called then assign the saved images to your ImageView.
You can save your MutableDictionary as a plist in the sandbox's application like that
[yourMutableDictionary writeToFile:pathToSave atomically:YES];
And then, get your NSMutableDictionary by this way
NSMutableDictionary *containOfPlist = [NSMutableDictionary arrayWithContentsOfFile:pathToYourPlist];
Hope this is what you want.
please use the NSCache for storing the images with KeyValue pair.
On the memory warning write the NSCache object to file and clear all objects from NScache object on low memory warning call back.
on loading application again rad the NSCAche from that file.
so i that manner you can save the data and can use it back.
another alternate option is to take NSDictnaory and store it via using NSArchiver class.
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.
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)
I am developing an application which has almost 12 view controllers. Application has tabBar with 4 tabs means four view controllers and there are multiple views to navigate in each tab bar. Please note each tab has a navigationController with a rootviewController being its firstview. Application is an extensive database application. When I run on simulator it works nice but get didRecieve memory warning while running on the device. I have few questions regarding the same.
NOTE: I have checked the application using Instruments tool for Leaks and there are no red pyramids which means there are no leaks in the code.
What is the best practise when you deal with multiple view controllers.
when I recieve memory warning, I call [self deleteObjects] which deletes all the instances which are retaning the values for the current controller. But I am not confident if this the right way. What should be done when we recieve memory warnings. Is there any good tutorial for that. (Links plz)
Also how to make sure that the [self deleteObjcts] is not called for the visible controller.
(I was calling the deleteObjects method in viewDidUnload method before but since we do [super didRecieveMemoryWarning] it breaks the code as viewDidLoad is called in hierarch from top to bottom so once I deleted object for top viewController obviously there will be error for rest of the controllers.)
Some of the basic confusing questions for me are as follows:
Why the memory warnings are not consistent. Like I get them sometimes at the start whereas sometimes there are no warnings.
When we used [NSDate date], [UIImage imageNamed:#"..."], [NSString stringWithFormat] etc, we dont own these objects and we dont have to release them but how can we make sure these objects are relased when we recieve a memory warning.
I am using NSMutableArray at multiple places. In this arrays I store the [NSString StringWithFormat ], [UIImage imageNamed...] objects, so when I realease the arrays do I need to relase the objects in the arrays though I dont own them.
Though this is a big list of question but I appreciate your help and time since I am in last stage of my development I am facing these major challenges.
You receive memory warnings when you run out of memory. Memory is not only taken by your App, all other running processes use memory and the memory being used may always vary.
Those object are all autoreleased. The NSAutoreleasePool will take care of releasing the objects, you should never release such an object yourself (well, not as long as you have not retained it yourself). This doesn't really matter, autoreleased object will be released quite quickly.
When putting an object in an array, it will be retained. When you release the array, it will send release all it's children objects. As you store autoreleased objects in the array, they will take care of their own releasing. This has probably already happened when you release the array, so -dealloc will be called on all the objects immediately.
NSMutableArray *someArray = [[NSMutableArray alloc] init];
NSDate *date = [NSDate date]; // Autoreleased object. Retain-count is 1
[someArray addObject:data]; // The array retains the data object. Now has a retain-count of 2
// Some other things
// The date object has been called release at some time (because it was autoreleased)
// so date now has a retain-count of 1
[someArray release] // Will release all containing objects thus date will be called dealloc
I've run into memory problems with UIImage imageNamed that I think are caused by the OS caching images and not releasing them when it should. There are lots of other developers who have seen the same thing.
I would try using imageWithContentsOfFile instead of imageNamed and see what happens.
For example forPNG images --
[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"filename here" ofType:#"png"]];
Here's a thread that covers what I'm talking about:
Whenever I add a new viewController my ObjectAlloc jumps up really high and never comes back down. Even after calling removeFromSuperview. Is this normal?
if((UIButton *) sender == gameArcadeBtn) {
GameArcade *gameArcadeController = [[GameArcade alloc]
initWithNibName:#"GameArcade" bundle:nil];
self.gameArcade = gameArcadeController;
[gameArcadeController release];
[self.view insertSubview:gameArcadeController.view atIndex:1];
}
Instantiating a view always creates many objects.As long as this view is in memory or has not been autoreleased, the objects will remained alloced in memory. Thus, to answer your question, this is normal.
It sounds like you are worried about memory usage and while it is important to watch the object allocs so that it doesn't get too it is more important to find your app leaks.
Some memory management tips:
1) do lazy loading. Only load your views when the user asks for them, not all at the beginning of the app
2) remove everything that you possibly can when you dont need it anymore. This means doing tons of work in viewWillAppear and viewDidDisappear
3) learn about #properties and how it relates to autoreleasing, and do not use properties for everything.
4) As appealing as it is, avoid autorelease and manually release objects when you dont need them anymore.
that's probably due to the fact that you're still retaining the view's controller in the class. try releasing that