unloading views for memory

Discussion in 'iOS Programming' started by larswik, Nov 15, 2012.

  1. larswik macrumors 68000

    Joined:
    Sep 8, 2006
    #1
    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?
     
  2. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #2
    There are various caches that might speed up loading the second time. How do you load the images?
     
  3. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #3
    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.
     
  4. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #4
    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
     
  5. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #5
    I read about it and it does say
    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?
     
  6. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #6
    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.
     
  7. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #7
    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!
     
  8. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #8
    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.
     
  9. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #9
    Why are you trying to avoid using the cache? It's there to help you, not hinder you.
     
  10. MattInOz macrumors 68030

    MattInOz

    Joined:
    Jan 19, 2006
    Location:
    Sydney
    #10
    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.
     
  11. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #11
    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.
     
  12. samdev macrumors regular

    Joined:
    Sep 16, 2011
    #12
    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.
     
  13. larswik thread starter macrumors 68000

    Joined:
    Sep 8, 2006
    #13
    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.
     
  14. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #14
    You might want to look into how imageNamed: automatically adds "@2x" onto the filename when running on a retina-display device.
     
  15. samdev macrumors regular

    Joined:
    Sep 16, 2011
    #15
    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.
     

Share This Page