Memory Management Issue

Discussion in 'iOS Programming' started by ArtOfWarfare, Sep 12, 2011.

  1. ArtOfWarfare macrumors 604

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #1
    I have a pretty big method here that I think has a leak somewhere.

    Running the allocation instrument shows that every time a new "category" is passed to this method, the amount of memory being taken up increases. This shouldn't happen, as concurrently with a "category" being opened, another one is getting closed, and thus the amount of memory being taken up should remain fairly steady.

    Something interesting about it is that the memory being taken up only increases if the category has not yet been opened before. Does anyone have any suggestions as to why this would be?

    Header with my IVars.
    Code:
    @interface Keyboard : UIScrollView
    {
        NSArray         *characters;
        NSMutableArray  *favorites;
        NSTimer         *holdTimer;
        BOOL            justFavored;
        BOOL            timerToStop;
        BOOL            viewingFavorites;
        UIButton        *spaceKey;
        UIButton        *deleteKey;
    }
    The method:
    Code:
    - (void)openCategory:(NSString*)category
    {
        [self.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop)
         {
             if ([obj isKindOfClass:[UIButton class]])
             {
                 [obj removeFromSuperview];
             }
         }];
        
        if ([category isEqualToString:@"Favorites"])
        {
            characters = favorites;
            self.backgroundColor = [UIColor colorWithRed:0.72 green:0.53 blue:0.15 alpha:1.0];
            viewingFavorites = YES;
        }
        
        else
        {
            NSString *path = [[NSBundle mainBundle] pathForResource:category ofType:@"txt"];
            NSString *string = [NSString stringWithContentsOfFile:path encoding:NSUTF16BigEndianStringEncoding error:NULL];
            characters = [string componentsSeparatedByString:@"\n"];
            self.backgroundColor = [UIColor colorWithRed:0.22 green:0.33 blue:0.83 alpha:1.0];
            viewingFavorites = NO;
        }
        
        [characters enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop)
         {
             UIButton *key = [UIButton buttonWithType:UIButtonTypeRoundedRect];
             key.frame = CGRectMake((index%6)*(self.frame.size.width/6.0),
                                    (floor(index/6.0))*(self.frame.size.width/6.0),
                                    self.frame.size.width/6.0,
                                    self.frame.size.width/6.0);
             [key setTitle:obj forState:UIControlStateNormal];
             [key setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
             if ([favorites containsObject:obj])
             {
                 [key setBackgroundImage:[UIImage imageNamed:@"favorNormal"] forState:UIControlStateNormal];
                 [key setBackgroundImage:[UIImage imageNamed:@"favorPress"] forState:UIControlStateHighlighted];
             }
             else
             {
                 [key setBackgroundImage:[UIImage imageNamed:@"keyNormal"] forState:UIControlStateNormal];
                 [key setBackgroundImage:[UIImage imageNamed:@"keyPress"] forState:UIControlStateHighlighted];
             }
             [key addTarget:self action:@selector(startTimer:) forControlEvents:UIControlEventTouchDown];
             [key addTarget:self action:@selector(stopTimer) forControlEvents:UIControlEventTouchDragExit];
             [key addTarget:self action:@selector(keyPress:) forControlEvents:UIControlEventTouchUpInside];
             [self addSubview:key];
         }];
        
        spaceKey = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        spaceKey.frame = CGRectMake(self.frame.size.width/6.0,
                                 (ceil([characters count]/6.0))*(self.frame.size.width/6.0),
                                 self.frame.size.width*2.0/3.0,
                                 self.frame.size.width/6.0);
        [spaceKey setTitle:@" " forState:UIControlStateNormal];
        [spaceKey addTarget:self action:@selector(keyPress:) forControlEvents:UIControlEventTouchUpInside];
        [self addSubview:spaceKey];
        
        deleteKey = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        deleteKey.frame = CGRectMake(self.frame.size.width*5.0/6.0,
                                 (ceil([characters count]/6.0))*(self.frame.size.width/6.0),
                                 self.frame.size.width/6.0,
                                 self.frame.size.width/6.0);
        [deleteKey setTitle:@"⌫" forState:UIControlStateNormal];
        [deleteKey setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
    	[deleteKey setTitleColor:[UIColor redColor] forState:UIControlStateHighlighted];
    	deleteKey.titleLabel.font = [UIFont fontWithName:@"Helvetica-Bold" size:17.0];
        [deleteKey addTarget:self action:@selector(deletePress) forControlEvents:UIControlEventTouchUpInside];
        [self addSubview:deleteKey];
    	
    	if (viewingFavorites == YES)
    	{
    		[spaceKey setBackgroundImage:[[UIImage imageNamed:@"favorNormal"] stretchableImageWithLeftCapWidth:25 topCapHeight:0] forState:UIControlStateNormal];
    		[spaceKey setBackgroundImage:[[UIImage imageNamed:@"favorPress"] stretchableImageWithLeftCapWidth:25 topCapHeight:0] forState:UIControlStateHighlighted];
    		[deleteKey setBackgroundImage:[UIImage imageNamed:@"favorNormal"] forState:UIControlStateNormal];
    		[deleteKey setBackgroundImage:[UIImage imageNamed:@"favorPress"] forState:UIControlStateHighlighted];
    	}
    	else
    	{
    		[spaceKey setBackgroundImage:[[UIImage imageNamed:@"keyNormal"] stretchableImageWithLeftCapWidth:25 topCapHeight:0] forState:UIControlStateNormal];
    		[spaceKey setBackgroundImage:[[UIImage imageNamed:@"keyPress"] stretchableImageWithLeftCapWidth:25 topCapHeight:0] forState:UIControlStateHighlighted];
    		[deleteKey setBackgroundImage:[UIImage imageNamed:@"keyNormal"] forState:UIControlStateNormal];
    		[deleteKey setBackgroundImage:[UIImage imageNamed:@"keyPress"] forState:UIControlStateHighlighted];
    	}
        
        self.contentSize = CGSizeMake(self.frame.size.width, ((ceil([characters count]/6.0))+1)*(self.frame.size.width/6.0));
    	[self flashScrollIndicators];
    }
    Any help would be much appreciated!
     
  2. RonC macrumors regular

    Joined:
    Oct 18, 2007
    Location:
    Chicago-area
    #2
    (Does the leaks tool indicate that they're leaks? If they're not leaks, it probably means they memory is being saved in a collection that you still have a reference to; if they are leaks, it will show you places where the object was created, retained, and released.)

    The only thing that jumps out at me at all is when you remove the buttons from the superview, do those objects now have a retainCount that makes sense? Do you need to add a [obj release] after the removeFromSuperview?

    That doesn't exactly make sense to me, since the buttons are created using a method that shouldn't retain them (buttonWithType:)
     
  3. ArtOfWarfare, Sep 12, 2011
    Last edited: Sep 12, 2011

    ArtOfWarfare thread starter macrumors 604

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #3
    I thought that maybe the issue was with the buttons like you suggested, but releasing them in the enumeration block causes the app to crash, so I guess not.

    I haven't tried it with the leaks tool, I suppose I'll do that now.

    Edit: Okay, trying with the leaks tool reveals no leaks. So I guess my code is sound?

    New question:

    How do I find out if my code leaks when it's closing? IE, if I alloc/init stuff in the init method, but in the dealloc method don't dealloc the stuff I own, I've leaked, right? But if the app is terminating at that moment, there's no way for instruments to catch that, is there?
     
  4. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #4
    The red-hilited code is assigning an unowned object to an ivar.

    I have no idea if this is significant or not. If no other method uses the characters array, it's not significant, and characters is better off being a local variable instead of an instance variable (ivar). If another method uses the characters array, then you could have problems with the under-retained array being dealloc'ed.

    You have a similar issue with the ivars spaceKey and deleteKey. Similarly, I have no idea if it's significant or not.

    In general, if you don't have a reason for multiple methods to refer to an ivar, or the ivar doesn't need to exist across multiple method invocations, the variable should be moved into the one method that uses it, where it will become a local variable.


    On looking for your leak, have you done a Build and Analyze?
     
  5. ArtOfWarfare thread starter macrumors 604

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #5
    Hmmm... looking through my code, characters doesn't need to be an ivar, so I changed it to a local variable.

    Those get used in other methods. While I'm sure I could come up with some work around for ivars (IE: assign tags to them,) I think ivars will work fine.

    Yes. Instruments reports no leaks right now.

    A question I have is, though, is whether or not Instruments will report leaks that occur while my program is closing? IE, if I alloc/init an array in my class init method, but then forget to include the release in the class dealloc method, would that cause a leak and would Instruments report it or not notice it?
     
  6. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #6
    Then you need to retain and release ownership of them at the proper points in time. Perhaps properties with the retain attribute would be suitable. In the current code, their ongoing existence is accidental: some other object owns them, so you're relying on a side-effect.


    The question is unclear. When your program terminates, its entire memory is reclaimed by the OS, regardless of whether you release or dealloc any of it.

    AFAIK, Instruments does nothing special at program termination. Probably because it can't. There can be many objects in existence at that time, which are never going to be release'd or dealloc'ed, yet which aren't leaks. For example, the UIApplication object (a singleton).

    Leaks are only leaks when they persist over time, or occur repeatedly. So run the program long enough, and Instruments will show the probable leaks. It's not perfect, either. It can show something as leaking when it isn't.
     
  7. North Bronson macrumors 6502

    Joined:
    Oct 31, 2007
    Location:
    San José
    #7
    You could:

    [1]
    Create a navigation controller in your app delegate and make this your root view controller.

    [2]
    Push a very simple view controller to your navigation controller. This controller displays a button. When the user pushes the button, you push an instance of the controller you want to test for leaks.

    [3]
    Pop the new controller off of the navigation stack and check for leaks. You can repeat this test and see if your memory use increases with time.

    Another handy trick is to add a button that displays a modal controller to cover the screen. After the modal controller is showing, cause a memory warning. The parent view controller should unload its view. When you dismiss the modal controller, the parent view controller should load its view. You can test for leaks each time.
     
  8. Sykte macrumors regular

    Joined:
    Aug 26, 2010
    #8
    If you lower the auto detection time or press the check for leaks now, it may. However as chown33 said it's almost a moot point because all memory; leaked or not will be freed. Your best bet if you feel there is a leak create a test application but make sure that piece of code is then destroyed.
     

Share This Page