Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

larswik

macrumors 68000
Original poster
Sep 8, 2006
1,552
11
Quick question. As I am testing my iPad app every now and then I get a low memory warning. It's not often at all every few days days of testing it pops up. I started to look at how to unload things from memory while I am using ARC.

On my mainVC I have a help button that loads a scrollView with about 20 jpg files that users scroll and can read about the features on the app. When the user first presses the button it takes almost 2 seconds till that new viewController is pushed on the stack. I then press the back button on the NavController to return to the main page. But when I press the help button again the page that took a couple of seconds to load the first time around loads right away. To me this would indicate that the VC and images were not unloaded on exiting the page to free up memory since it loads right away?

I thought when you backed out of a View that view is destroyed?
 
There are various caches that might speed up loading the second time. How do you load the images?

Like so, this is just a few of them

Code:
- (void)viewDidLoad
{
    [super viewDidLoad];
    self.navigationController.navigationBar.hidden = NO;
    self.scrollView.contentSize = CGSizeMake(pageSize * 9,748);
    self.scrollView.bounces = NO;
    
    UIImage *image1 = [UIImage imageNamed:@"help0.jpg"];
    UIImageView *image1View = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 1024, 748)];
    image1View.image = image1;
    [self.scrollView addSubview:image1View];
    
    UIImage *image2 = [UIImage imageNamed:@"help1.jpg"];
    UIImageView *image2View = [[UIImageView alloc] initWithFrame:CGRectMake(pageSize, 10, 1024, 748)];
    image2View.image = image2;
    [self.scrollView addSubview:image2View];
    
    UIImage *image3 = [UIImage imageNamed:@"help2.jpg"];
    UIImageView *image3View = [[UIImageView alloc] initWithFrame:CGRectMake(pageSize * 2, 10, 1024, 748)];
    image3View.image = image3;
    [self.scrollView addSubview:image3View];
}

I was making my original jpg files 2x at 2048 x 1536 for retina. I scaled back to 1024 x 748. Load time is faster and the memory warning I had launching this viewController is gone. I have created about 8 scrollView pages that are 1024 x 748 (space for navBar 748). When I am done I will have about 17 to 20 1024 x 748 images for the help section.
 
UIImage imageNamed places the images into an in-memory cache and appears to load the images fast. imageWithContentsOfFile: doesn't use the image cache. Apparently when images are set in a nib or storyBoard the images are loaded using imageNamed.

It usually makes sense to load smaller images into the cache but not larger images. There's no direct way to clear the cache but it is reported to empty when there's a memory warning.

Images take up memory based on the dimensions of the image: hxwx4 so you can calculate
 
I read about it and it does say
Discussion
This method does not cache the image object.

Just for a little more clarification. Using the imageNamed creates my object and stores it in memory cache. When I read my C book a couple of years ago it talked about Heap memory. Is the cache the heap section of memory or something different?

So if I want to completely unload this viewController when the user backs out of it I want to use the imageWithContentsOfFile:. This will create my objects and store them in memory that is destroyed when the user backs out of a view.

Is my understanding of that correct?
 
The UIImage image cache uses heap memory. I doesn't cache the UIImage objects but it caches the in-memory representation of the images. So if you were to have multiple UIImage objects that referenced the same file there would only be a single copy of the image in memory.

If you use imageWithContentsOfFile: the in-memory representation of the image is destroyed when the UIImage object is destroyed. This happens if the imageView's image property is set to nil or if the view controller's view hierarchy is destroyed. Normally this happens when the view controller is popped.
 
PhoneyDeveloper, thank you for the detailed explanation. I get it now. It gives me a better understanding of what is happening with memory. When ever I build an app or test things out using images I always rely on imageNamed: to get the image. Never questioning what is happening behind the scenes since it just works.

Thanks for shedding some light on Heap memory and how things are stored!
 
Success! Now when I click on the button it lags just a little so I know it is not cached. I also found a way of making the code smaller, more efficient with a fast enumerator. So instead of duplicating the code above for each image I had I do this now.

Code:
- (void)viewDidLoad
{
    [super viewDidLoad];

    NSArray *imageNameArray = @[ @"help0",@"help1",@"help2",@"help3",@"help4",@"help5",@"help6",@"help7",@"help8",@"help9" ];
    
    self.navigationController.navigationBar.hidden = NO;
    self.scrollView.contentSize = CGSizeMake(pageSize * [imageNameArray count],748);
    self.scrollView.bounces = NO;
    
    int counter = 0;
    NSString *bundlePath = [[NSBundle mainBundle]bundlePath];
    
    for (NSString *aKey in imageNameArray) {
        NSString *imageLocationString = [NSBundle pathForResource:aKey ofType:@"jpg" inDirectory:bundlePath];
        
        UIImage *image = [UIImage imageWithContentsOfFile:imageLocationString];
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(pageSize * counter, -5, 1024, 748)];
        imageView.image = image;
        [self.scrollView addSubview:imageView];
        counter++;
    }

So now as I add images I just need to add a new index to the Array.
 
This might be a dumb question, but isn't the 2 second load time for the help view the real problem?

I mean the help view takes 2 seconds to load the first time I know I'm not coming back to see that it's faster the second time.
 
I was getting memory warnings and I noticed that when I first clicked on a UIButton it took 2 seconds to push the new view on the stack. But when I backed out and re clicked it it loaded right away. So the original question turned more in to learning more about memory.

I thought a view that came off the stack was destroyed freeing up memory so it should not have loaded so fast again. So success was more or less understanding how things worked memory wise.

So weather it was to help or hinder it was more or less a learning opportunity. I have the tendency to not learn when something works. I have always relied on imageNamed which caches images and now imageWithContentsOfFile: offers me another way to do it.

That's all.
 
I was making my original jpg files 2x at 2048 x 1536 for retina. I scaled back to 1024 x 748. Load time is faster and the memory warning I had launching this viewController is gone. I have created about 8 scrollView pages that are 1024 x 748 (space for navBar 748). When I am done I will have about 17 to 20 1024 x 748 images for the help section.

JPGs stored on file may be small, but JPGs stored in memory are most likely uncompressed. So, if you're loading up
20 of these photos, that's like 20*1024*768*4 = 60 MB.

And BTW, imageWithContentsOfFile doesn't load Retina images like imageNamed. At least not for me. If I don't
provide the exact filepath to the file, it fails.
 
Huh, that's a lot of memory. I suppose it would be best to break up the help menu in to more manageable sections.

I am testing on an iPad 1 with 256 megs of ram. I am sure not all of that is mine for the taking the the system and other apps left open.

I am confused as to why it would not load retina images? A Jpeg is a jpeg and if it has the proper pixel density for retina displays why imageNamed: and imageWithContentsOfFile: would make a difference bring the image to the screen? The only real difference in my code is how I get the image from the NSBundle. It is still passed to a UIImageView object.

That's odd.
 
Huh, that's a lot of memory. I suppose it would be best to break up the help menu in to more manageable sections.

I am testing on an iPad 1 with 256 megs of ram. I am sure not all of that is mine for the taking the the system and other apps left open.

Well, seeing is believing. XCode4 has some tools to monitor total memory usage.

Just run Profile and select Activity Monitor, you can see total usage in "Real Memory Usage".
The name of your app should be listed there.

But, I find it easier to view stats on a real device rather than the simulator, because the Activity Monitor
includes stats on everything, and MacOSX is loaded with extra processes and other garbage. The stats are
much cleaner while running the debugger on a real device.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.