Greg
Greg

Reputation: 1842

Is this memory leak with UISnapBehavior a false alarm?

I am trying to animate a UIButton using UISnapBehaviour. I followed Apple References and the Apple Dynamics Catalog, but instead of using a tap gesture to start the animation, an animated start button appears automatically when the view launches. My code runs in the simulator, i.e. animation works and so does the button when animation ends. But even though the animation appears to work fine, the Analyser in Xcode reports a memory leak.

I tried relocating [self.animator addBehavior:_snapButton]; and [self.animator removeBehavior:_snapButton]; in several places where the animation still works and it feels like I am do everything correctly. But no matter what I do, I cannot get the retain count to 0. I'm starting to suspect there could be a bug in the Analyser.

Here is my code. I declared the properties

@interface WaitView ()
    @property (nonatomic, strong) UIDynamicAnimator *animator;
    @property (nonatomic, strong) UISnapBehavior *snapBehavior;
    @end

then initialised UIDynamicAnimator after [super viewDidLoad]

- (void)viewDidLoad {
    [super viewDidLoad];        //  then tried  [self startButton];
    [self startButton];         // followed by  [super viewDidLoad];

    UIDynamicAnimator *animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    self.animator = animator;
    [animator release];
}   // Wait view

and created a UIButton before animating it

- (void)startButton {
    CGPoint snapPoint        = CGPointMake(160,284);
    UIButton *wait           = [UIButton buttonWithType:UIButtonTypeCustom];
    wait.frame               = CGRectMake(0, 0, 80, 80);
    [wait setBackgroundImage:[UIImage imageNamed:[NSString stringWithFormat:@"familyDot%d", 1]] forState:UIControlStateNormal];
    wait.tag                 = 1;
    [wait setTitle:[NSString stringWithFormat:@"%i", 1] forState:UIControlStateNormal];
    [wait setTitleColor:[UIColor blackColor] forState: UIControlStateNormal];
    wait.titleLabel.hidden  = NO;
    [wait addTarget:self action:@selector(tapTest) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:wait];

    [self.animator removeAllBehaviors];
    UISnapBehavior* _snapButton = [[UISnapBehavior alloc] initWithItem:wait snapToPoint:snapPoint];
    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
    [self.animator addBehavior:_snapButton];
    [_snapButton release];
}


EDIT Based on Kurt's response, the solution is to replace the last five lines of my animation code above with the following

    UISnapBehavior* _snapButton = [[[UISnapBehavior alloc] initWithItem:wait snapToPoint:target] autorelease];
    self.animator = [[[UIDynamicAnimator alloc] initWithReferenceView:self.view] autorelease];
    [self.animator addBehavior:_snapButton];


and included a test for the button.

- (void)tapTest {
    NSLog(@“Start button works!");
}

Here is a breakdown of the memory leak reported by the Analyser:

    self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view]; 

1. Method returns an Objective-C object with a +1 retain count

    [self.animator addBehavior:_snapButton];

2. Object leaked: allocated object is not referenced later in this execution path and has a retain count of +1

Can anyone please tell me if this is really a memory leak ? Or if I am doing something wrong ?

Upvotes: 0

Views: 95

Answers (1)

Kurt Revis
Kurt Revis

Reputation: 27994

Yes, this will cause a leak. The problem is all on one line:

self.animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];

[[UIDynamicAnimator alloc] initWithReferenceView:self.view] returns an object with a +1 reference count. It is your responsibility to release it when you're done with it.

Then you perform self.animator = that object. Since it's a strong property, it releases the old object (if any) and retains the new one. So your object now has a +2 retain count.

Then you return from the method. Assuming you follow the usual memory management rules and release your strong property later, you will only release the object once. It will still have a +1 retain count and you'll leak it.

To fix it, do exactly what you did in -viewDidLoad:

 UIDynamicAnimator *newAnimator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
 self.animator = newAnimator;
 [newAnimator release];

Or:

 self.animator = [[[UIDynamicAnimator alloc] initWithReferenceView:self.view] autorelease];

Or convert to ARC! It would have done this all for you.

Upvotes: 1

Related Questions