2

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 :)

Matt Mc
  • 8,882
  • 6
  • 53
  • 89

2 Answers2

1

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);
    }
};
Community
  • 1
  • 1
Matt Mc
  • 8,882
  • 6
  • 53
  • 89
0

Blocks are created on the stack. You need to copy completionBlock to the heap so you can be sure it will still be valid when you try to run it. Just put this at the top of your method:

completionBlock = [completionBlock copy];

Note that if completionBlock is already on the heap, this just returns the same heap copy.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Hm...I appreciate your fast answer. I have a question: is copying a block still necessary under ARC? (It seems not.) For the sake of argument, how exactly would I go about calling copy on the two blocks in my above example if were *not* running under ARC? Just once on the `completionBlock` at the top of the method, but not to the `compoundBlock` because it itself would be copied internally by the UIView animation call? Thanks. – Matt Mc Oct 07 '12 at 03:43
  • If not under ARC, each `copy` must be balanced by a `release` or `autorelease`. In many cases it is not necessary to `copy` a block under ARC, but there are still cases where it is necessary, so I tend to do it prophylactically. – rob mayoff Oct 07 '12 at 03:45
  • 1
    This is Not True. He does not need to explicitly copy the block `completionBlock` in the example given, in MRC or ARC. The method does not store `completionBlock` for later use anywhere except being captured by the other block `compoundBlock`. When (and if) `compoundBlock` gets copied, it will automatically copy `completionBlock`. – newacct Oct 08 '12 at 08:38