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

ArtOfWarfare

macrumors G3
Original poster
Nov 26, 2007
9,704
6,296
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!
 
(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:)
 
(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:)

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?
 
Last edited:
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];
[COLOR="Red"]        characters = [string componentsSeparatedByString:@"\n"];
[/COLOR]        self.backgroundColor = [UIColor colorWithRed:0.22 green:0.33 blue:0.83 alpha:1.0];
        viewingFavorites = NO;
    }
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?
 
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.

Hmmm... looking through my code, characters doesn't need to be an ivar, so I changed it to a local variable.

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

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.

On looking for your leak, have you done a Build and Analyze?

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?
 
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.

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.


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?
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.
 
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?

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.
 
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?

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.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.