Wednesday, May 18, 2011

Some retainCount Love

When you design, you're dreaming what could happen. When you code, you're experimenting what can happen. When you debug, you're learning what is happening.
- (void)dealloc
{
  NSLog(@"%u", [myView retainCount]); // 3
  [self.myView removeFromSuperview];
  NSLog(@"%u", [myView retainCount]); // 2
  [myView release];
  NSLog(@"%u", [myView retainCount]); // 1
  myView = nil;
  NSLog(@"%u", [myView retainCount]); // 0
  [super dealloc];
}

- (void)viewWillAppear:(BOOL)animated
{
  [super viewWillAppear:animated];
  self.myView = [[MyView alloc] initWithFrame:frame];
  NSLog(@"%u", [self.myView retainCount]); // 2
  [self.view addSubview:self.myView];
  NSLog(@"%u", [self.myView retainCount]); // 3
}
Love your retainCount, cherish it. It's something you can count on.

UPDATE: Several people have pointed out here and elsewhere that my sample code is wrong. Seems like I fixed a wrong problem in a wrong way and got right results by mistake!

Really appreciate this! Love learning from mistakes, so I've left the "buggy" code here as a reminder. There's nothing wrong with making mistakes - as long as you learn from them. Repeating same old mistakes is waste of time, when there are so many wonderful new ones to discover!

Nevertheless this blog posting was to praise retainCount. Without it I would not have been able to get rid of memory leak, regardless that I fixed wrong problem in wrong way ;-)

9 comments:

  1. Interesting. What is your conclusions on this dealloc method? What if you did a myView release then myView = nil? First time I see code doing something more than just release an object in dealloc... why?

    ReplyDelete
  2. The problem was that in viewWillAppear method I didn't release myView, but kept it in property. Usually you'd do [myView release] and access it afterwards via viewWithTag, but now I "optimized". Updating that view's properties 3 times a second, while system busy doing something else... Real lesson learned: do not optimize until really needed :) Anyway, retainCount revealed the memory leak.

    ReplyDelete
  3. Unfortunately your code and tests are wrong...

    - (void)dealloc
    {
    NSLog(@"%u", [myView retainCount]); // 3, ok

    [self.myView removeFromSuperview]; // Strange to make a removeFromSuperview in a dealloc. When self.view will be released, it will automatically remove all its subviews
    NSLog(@"%u", [myView retainCount]); // 2, still ok

    [myView release];
    NSLog(@"%u", [myView retainCount]); // 1, this log stating that there still is an object retaining it ! Normally if there were no memory leaks this call should crash the app because your are calling a method on a deallocated object...

    myView = nil;
    NSLog(@"%u", [myView retainCount]); // 0, this log is totally wrong and will always return 0 because you are sending the method call to nil !!! Try to move the two above lines at the beginning of the method !

    [super dealloc];
    }

    To convince you for the myView = nil try this :
    UIView *v = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
    [[[[[[v retain] retain] retain] retain] retain] retain];
    NSLog(@"before ===> %d", [v retainCount]); // 7
    v = nil;
    NSLog(@"after ===> %d", [v retainCount]); // 0

    Now if myView is a retained property:
    @property (nonatomic, retain) MyView *myView;
    And according to your log, it seems to be !
    Your viewWillAppear method is the source of the memory leak !

    self.myView = [[MyView alloc] initWithFrame:frame];
    The alloc/init will create a MyView with a retain count to 1, and then your self.myView will increase the count to 2 ! (Your log confirm that !)
    You should do
    self.myView = [[[MyView alloc] initWithFrame:frame] autorelease];

    Moreover, I would recommend you to create your subviews in the viewDidLoad method and to not forget to implement the viewDidUnload :

    - (void)dealloc
    {
    [myView release];
    [super dealloc];
    }

    - (void)viewDidLoad
    {
    [super viewDidLoad];

    self.myView = [[[MyView alloc] initWithFrame:frame] autorelease];
    [self.view addSubview:self.myView];
    }

    - (void)viewDidUnload
    {
    [super viewDidUnload];

    self.myView = nil;
    }

    Finally I recommend you to use Instrument to debug memory leaks, instead of logs with retainCount.

    Regards,
    Johann Fradj from HotAppsFactory

    ReplyDelete
  4. Thanx for good comments, have to check them properly! Now just a note that a) app did have memory usage related problem (no leak, but continuous over-allocation) b) verified that with Instruments c) found a fix with retainCount and d) verified the fix with Instruments.

    Still believe the problem was caused by not releasing myView, but saving that in property...

    ReplyDelete
  5. "have to check them properly" Check, check, check ;-)


    "Still believe the problem was caused by not releasing myView, but saving that in property..."

    I always retained my views and almost never used viewWithTag method and I have no memory leak nor continuous over-allocation problems !

    Your code have memory leaks problems because you are retaining myView twice and only release once... So the created instance of myView remains allocated and will never be released because your are no more pointer on it ! That is the perfect definition of memory leak ;-)

    Regards,
    Johann Fradj from HotAppsFactory

    ReplyDelete
  6. Thanx Johann, checked and fixed! Removed removeFromSuperview and added [myView release] in viewWillAppear.

    Instrument still says everything is ok, but I know code is now better :)

    ReplyDelete
  7. Oh btw, why did I do myView = nil;? Because that speeds up crashing, in case I try to use a "released" object afterwards. Didn't know it would also reset retain count to zero, so that was another useful lesson learned! Good to remember!

    ReplyDelete
  8. Hi jouni,

    Really appreciate your update! Indeed there's nothing wrong with making mistakes, and we are here to help each others ;-)

    "Oh btw, why did I do myView = nil;? Because that speeds up crashing, in case I try to use a "released" object afterwards"

    Ok but if your program is not on a multi threaded, the problem cannot occurred (of course except programming mistakes) and if the program is multi threaded, this kind of crashes may revealed weird race conditions so setting the property to nil just "hide" the problem from you but it will not really solved it. It can be enough but it's good to understand why.

    Regards,
    Johann Fradj from HotAppsFactory

    ReplyDelete
  9. retainCount is a horrible tool, even for debugging. There are a slew of much better tools available. The Allocations instrument, the static analyzer, etc...

    ReplyDelete