Search code examples
cocoa-touchmemory-managementcore-animationobjective-c-blocksuianimation

Recursive method containing UIAnimation block, how to properly release


I did a small "loader" that I can stick on the UI when I need it. Much like the UISpinnerView.

It has some logic (I simplified the code in the block for this post) that require it to be recursively called. I do it like this:

- (void) blink {

    tick++;

    if (tick > kNumberOfLights)
        tick = 0;

    UIView *lightOne = [self viewWithTag:kLightStartIndex];
    UIView *lightTwo = [self viewWithTag:kLightStartIndex+1];   

    [UIView animateWithDuration:0.5
                          delay: 0.0
                        options: UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveEaseOut
                     animations:^{

                         if (tick == 0) {

                             [lightOne setAlpha:kLightOn];
                             [lightTwo setAlpha:kLightOff];                            

                         } else if (tick == 1) {

                             [lightOne setAlpha:kLightOff];
                             [lightTwo setAlpha:kLightOn];    
                         }
                     }
                     completion:^(BOOL finished){
                             [self blink];    
                     }];

}

The method [self blink] is called when the view is added to a super view.

There are no objects retained in the Loader class, so when I remove it in the super view it is released. The problem is that if the animation block is running when I release the view, the completion block will call a deallocated object and cause the error:

(20380,0xa0a29540) malloc: * mmap(size=2097152) failed (error code=12) * error: can't allocate region

In the console and the error:

__[LoaderClass blink]_block_invoke_2

in the Debug Navigator.

How do I make sure to correctly tear down the view when it is removed from the super view?


Solution

  • Overriding release is usually a bad idea, but this case might be an exception to the rule.

    Instance variables:

    BOOL isBlinking;
    BOOL releaseWhenDoneAnimating;
    

    Initialize isBlinking = NO and releaseWhenDoneAnimating = NO.

    - (void)release
    {
        if(!isBlinking) {
            [super release];
            return;
        }
    
        releaseWhenDoneAnimating = YES;
    }
    
    - (void) blink {
    
        isBlinking = YES;
    
        if(releaseWhenDoneAnimating) {
            [super release];
            return;
        }
    
        tick++;
    
        if (tick > kNumberOfLights)
            tick = 0;
    
        UIView *lightOne = [self viewWithTag:kLightStartIndex];
        UIView *lightTwo = [self viewWithTag:kLightStartIndex+1];   
    
        [UIView animateWithDuration:0.5
                          delay: 0.0
                        options: UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveEaseOut
                     animations:^{
    
                         if (tick == 0) {
    
                             [lightOne setAlpha:kLightOn];
                             [lightTwo setAlpha:kLightOff];                            
    
                         } else if (tick == 1) {
    
                             [lightOne setAlpha:kLightOff];
                             [lightTwo setAlpha:kLightOn];    
                         }
                     }
                     completion:^(BOOL finished){
                         [self blink];    
                     }];
    
    }