Scrolling/Image memory leak...

Discussion in 'iOS Programming' started by MentalFabric, Apr 23, 2010.

  1. MentalFabric macrumors 6502

    MentalFabric

    Joined:
    Mar 10, 2004
    #1
    Hi all,

    Having recently taken up app development, i've reached the stage where my app does what I want it to do, and I'm trying to fix any bugs that exist. Unfortunately, I've got a memory leak...

    I have a nav bar in a tab app, each row of the nav bar's table view contains a link to a new view, which all contain a row of images within a scrollview. I'm pretty sure this is where my problem is:

    Code:
    - (void)viewDidLoad
    {
    self.view.backgroundColor = [UIColor blackColor];
    // 1. setup the scrollview for multiple images and add it to the view controller
    //
    // note: the following can be done in Interface Builder, but we show this in code for clarity
    // [scrollView1 setBackgroundColor:[UIColor blackColor]];
    [scrollView1 setCanCancelContentTouches:NO];
    scrollView1.indicatorStyle = UIScrollViewIndicatorStyleWhite;
    scrollView1.clipsToBounds = YES; // default is NO, we want to restrict drawing within our scrollview
    scrollView1.scrollEnabled = YES;
    scrollView1.pagingEnabled = YES;
    scrollView1.bounces = YES;
    scrollView1.directionalLockEnabled = YES;
    scrollView1.maximumZoomScale = 1.0;
    scrollView1.minimumZoomScale = 1.0;
    scrollView1.multipleTouchEnabled = YES;
    scrollView1.delegate = self;
    
    
    // load all the images from our bundle and add them to the scroll view
    NSUInteger i;
    for (i = 1; i <= kNumImages; i++)
    {
    NSString *imageName = [NSString stringWithFormat:@"image%d.png", i];
    UIImage *image = [UIImage imageNamed:imageName];
    UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
    
    ^^ i'm pretty sure the problem is that the image(x).png is not being released, but I can't figure out where I can release it - should i add it to the .h file so i can dealloc it at the end?
    Code:
    // setup each frame to a default height and width, it will be properly placed when we call "updateScrollList"
    CGRect rect = imageView.frame;
    rect.size.height = kScrollObjHeight;
    rect.size.width = kScrollObjWidth;
    imageView.frame = rect;
    imageView.tag = i; // tag our images for later use when we place them in serial fashion
    [scrollView1 addSubview:imageView];
    [imageView release];
    
    }
    [self layoutScrollImages];
    }
    
     
  2. kainjow Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #2
    That code looks fine. How are you determining there's a leak?
     
  3. MentalFabric thread starter macrumors 6502

    MentalFabric

    Joined:
    Mar 10, 2004
    #3
    Thanks for your response! other than the crash, when I run with the Leaks performance tool, it highlights the line
    Code:
    UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
    
    as leaking the number of UIImageViews that there are number of images on that particular view when the view opens...

    The only other thing I can think of is that it's not closing the views properly... here's how i'm opening them in my tableviewcontroller:

    Code:
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
     
     
     NSInteger row = [indexPath row];
    
     
       if([[booksArray objectAtIndex:row] isEqual:@"Contents"])
       {
        AboutViewController *aBookDetail = [[AboutViewController alloc] initWithNibName:@"AboutView" bundle:nil];
        self.aboutViewController = aBookDetail;
        self.hidesBottomBarWhenPushed = YES;
        [aBookDetail release];
        aboutViewController.title = [NSString stringWithFormat:@"%@", [booksArray objectAtIndex:row]];
        [self.navigationController pushViewController:aboutViewController animated:YES];
       } 
       else
        if([[booksArray objectAtIndex:row] isEqual:@"Introduction"])
        {
         IntroductionView *bBookDetail = [[IntroductionView alloc] initWithNibName:@"IntroductionView" bundle:nil];
         self.introductionView = bBookDetail;
         self.hidesBottomBarWhenPushed = YES;
         [bBookDetail release];
         introductionView.title = [NSString stringWithFormat:@"%@", [booksArray objectAtIndex:row]];
         [self.navigationController pushViewController:introductionView animated:YES];
         }
    
    then it uses the navigation controller back button to go back to the table view... each view has a release in the dealloc though I think maybe there could be some statement I should make after pushViewController that releases the view when the back button is pressed?

    It doesn't crash the simulator, just the device - i guess because it only just exceeds the memory limit and the simulator has more available resources...
     
  4. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #4
    In the dealloc method for the view controller that has been removed from the screen you should release all object that you currently own. So anything that you create with alloc/init that has not yet been released should be released there.
     
  5. MentalFabric thread starter macrumors 6502

    MentalFabric

    Joined:
    Mar 10, 2004
    #5
    I've got that much but I'm just not sure what i'm missing :confused: the imageView is released in the same section of code, I've tried making it a class and releasing it in the dealloc as well and I've tried just releasing UIImageView/UIImage in the dealloc but I can't figure out how all the images are staying stuck in the cache - I don't know what to release!

    *although I should mention the only thing in the dealloc at the moment is scrollView1
     
  6. MentalFabric thread starter macrumors 6502

    MentalFabric

    Joined:
    Mar 10, 2004
    #6
    bump:

    I think perhaps I need to set 'image' from this code
    Code:
    NSString *imageName = [NSString stringWithFormat:@"image%d.png", i];
    UIImage *image = [UIImage imageNamed:imageName];
    UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
    
    to nil, but if i do it in viewDidLoad it breaks and if I set it outside of viewDidLoad it gives me a 'not declared' error...

    Totally stuck here! :(
     
  7. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #7
    Why do you think that?
     
  8. MentalFabric thread starter macrumors 6502

    MentalFabric

    Joined:
    Mar 10, 2004
    #8
    oooh, this one is attracting all the demi-gods :D

    well, read this:

    http://www.alexcurylo.com/blog/2009/01/13/imagenamed-is-evil/

    I don't have a problem with the imageNamed on any particular view, I can open any one view as many times as I like without crashing the app, but for some reason the images aren't being released when the view changes... in that article it sounds as though all their images were in the same place so I figure I must have missed something...
     
  9. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #9
    imageNamed: caches images. Why would you expect them to be released?
     
  10. MentalFabric thread starter macrumors 6502

    MentalFabric

    Joined:
    Mar 10, 2004
    #10
    Well I got to the point of realising they weren't being released and started trying to figure out how to release them before closing the view. Which is how I got to figuring, perhaps setting image to nil would do it... I just can't figure out how to do that outside of the viewDidLoad section :/

    *edit: or inside the viewDidLoad section, specifying that it doesn't happen until the view closes...
     
  11. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #11
    If you want to be able to control when the images are released (i.e. you don't want them to be cached), then you shouldn't use imageNamed:. Consider using initWithContentsOfFile: instead. Then you can just release them once you've used them for the initWithImage:.
     
  12. MentalFabric thread starter macrumors 6502

    MentalFabric

    Joined:
    Mar 10, 2004
    #12
    Thanks for the tip! I've now swapped
    Code:
    NSString *imageName = [NSString stringWithFormat:@"image%d.png", i];
    UIImage *image = [UIImage imageNamed:imageName];
    UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
    
    with
    Code:
    NSString *path = [[NSBundle mainBundle] pathForResource:[NSString 
                     stringWithFormat:@"image%d",i] ofType:@"png"]; 
            UIImage *image = [UIImage imageWithContentsOfFile:path]; 
            UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
    
    but I get the same problem - what do I need to release, and where? :S
     
  13. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #13
    That should work.
     
  14. MentalFabric thread starter macrumors 6502

    MentalFabric

    Joined:
    Mar 10, 2004
    #14
    unfortunately that causes the view to open properly once, but crash the app if you choose to view the same view from the table again :/

    edit: Sorry! Missed the UIImage alloc - that'll probably get it working - thank you :D i've been fighting this problem for about half a week ^.^

    double edit: damnit once I put that in, it crashes when I select the table cell to open the view :'(
     
  15. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #15
    Where, in terms of the code, is it crashing?
     
  16. MentalFabric thread starter macrumors 6502

    MentalFabric

    Joined:
    Mar 10, 2004
    #16
    The console gives me -[UIImage imageWithContentsOfFile:]: unrecognized selector sent to instance 0x4249d80
    Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[UIImage imageWithContentsOfFile:]: unrecognized selector sent to instance 0x4249d80
    Stack:
    (Stack numbers)

    in the debug i get past -[IntroductionView viewDidLoad]
    to...
    __forwarding_prep_0___
    ___forwarding___
    -[NSObject doesNotRecognizeSelector:]
    objc_exception_throw
    ___TERMINATING_DUE_TO_UNCAUGHT_EXCEPTION___
     
  17. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #17
    dejo has thrown you a curve ball. imageWithContentsOfFile: is a class method, not an instance method so you should be calling it on UIImage as you were initially.
     
  18. MentalFabric thread starter macrumors 6502

    MentalFabric

    Joined:
    Mar 10, 2004
    #18
    ooh I'm so confused :confused: :D

    So if I set it back to use imageNamed, how can i set image to nil or otherwise release the cache?
     
  19. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #19
    As far as I am aware you can't. You have no control over the internal caching.
     
  20. MentalFabric thread starter macrumors 6502

    MentalFabric

    Joined:
    Mar 10, 2004
    #20
    There has to be some way to be able to use all these images throughout the different views without caching them all at once though right?!
     
  21. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #21
    Yes. As suggested above load them through imageWithContentsOfFile: and retain/release them yourself as required.
     
  22. MentalFabric thread starter macrumors 6502

    MentalFabric

    Joined:
    Mar 10, 2004
    #22
    Aha sorry didn't get you were just referring to the UIImage alloc...

    And it looks as though the crash that was still happening when I included [image release]; was a result of my attempting to release NSString *imageName in viewDidUnload :D bit more testing just to make sure but it looks like it works! You guys truly are demi-gods. thank you :D
     
  23. MentalFabric thread starter macrumors 6502

    MentalFabric

    Joined:
    Mar 10, 2004
    #23
    Sorry getting ahead of myself again... i'm still not releasing the images properly - any hints as to what and where?
     
  24. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #24
    When you are done with them. Basically when you won't have a reference to it yourself you need to ensure that you've released anything created with alloc/init or copy (but not any other method types).
     
  25. MentalFabric thread starter macrumors 6502

    MentalFabric

    Joined:
    Mar 10, 2004
    #25
    So what I have is
    Code:
    NSString *path = [[NSBundle mainBundle] pathForResource:[NSString 
                     stringWithFormat:@"image%d",i] ofType:@"png"]; 
            UIImage *image = [UIImage imageWithContentsOfFile:path]; 
            UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
    if I put [image release]; here the view will not open a second time... can't put it in the dealloc because it's not been declared... imageView is released fine after being added to the scrollview subview..

    Should I be declaring image somehow so that I can release it in the dealloc or in the viewDidUnload?
     

Share This Page