Search code examples
iphoneretainblock

Objective-C passing a block into a block


This is a bit of a tricky scenario. I've been studying blocks and started implementing them for the first time, and I found myself wanting to create a "compound block". Here's my code, roughly:

- (void)moveToPosition:(NSInteger)pos withVelocity:(CGFloat)vel onCompletion:(void(^)(BOOL completed))completionBlock
{

    void (^compoundBlock) (BOOL completed) = ^(BOOL completed) {
        [self unlockInteractionFromPullDownMenuTab];
        void(^innerCompletionBlock)(BOOL completed) = completionBlock;
        innerCompletionBlock(completed);
    };

    // Animate
    [UIView animateWithDuration: duration
                     animations: ^void{ [self.pullDownMenu setFrame:newFrame]; }
                     completion: compoundBlock
     ];


}

The goal is to take a block of code that is passed into this method, add something to it, and then pass it into an animation method call. However, I get a bad access on the line:

innerCompletionBlock(completed);

I figure that my innerCompletionBlock is getting deallocated, but I'm not entirely sure why. From what I understand, blocks copy everything that you throw at them, including references to self--which can create retain cycles, and which I recently learned to avoid.

Actually, I originally tried this:

void (^compoundBlock) (BOOL completed) = ^(BOOL completed) {
    [self unlockInteractionFromPullDownMenuTab];
    completionBlock(completed);
};

But I was getting the bad access, and I figured that perhaps the compoundBlock wasn't copying the completionBlock, so I explicitly declared a (block) variable inside the block and assigned it to try to get it to retain (perhaps a bit silly, but I'm running under ARC so I can't do manual retain calls).

Anyway, clearly the compoundBlock is being retained when it's passed to UIView, but I'm unsure how to retain my onCompletion/innerCompletionBlock within the compoundBlock since I'm running under ARC.

Thanks in advance :)


Solution

  • Aha, figured it out. Bit stupid, really.

    There are various times where I was calling the method - (void)moveToPosition:... and passing nil to the completionBlock parameter...because I just didn't need to do anything extra at the end of the animation and only wanted the [self unlockInteractionFromPullDownMenuTab]; that was tacked on in the compoundBlock.

    Makes sense, right?

    ...Only if you check for nil before you call the block. As discussed elsewhere on SO, "When you execute a block, it's important to test first if the block is nil". Well, I learned my lesson there.

    This code works:

    // Compound completion block
    void (^compoundBlock) (BOOL completed) = ^(BOOL completed) {
        [self unlockInteractionFromPullDownMenuTab];
        if (completionBlock != nil) {
            completionBlock(completed);
        }
    };