Recursive Blocks in Objective-C leaking in ARC

2019-01-18 10:55发布

问题:

So I'm using recursive blocks. I understand that for a block to be recursive it needs to be preceded by the __block keyword, and it must be copied so it can be put on the heap. However, when I do this, it is showing up as a leak in Instruments. Does anybody know why or how I can get around it?

Please note in the code below I've got references to a lot of other blocks, but none of them are recursive.

__block NSDecimalNumber *(^ProcessElementStack)(LinkedList *, NSString *) = [^NSDecimalNumber *(LinkedList *cformula, NSString *function){
        LinkedList *list = [[LinkedList alloc] init];
        NSDictionary *dict;
        FormulaType type;
        while (cformula.count > 0) {
            dict = cformula.pop;
            type = [[dict objectForKey:@"type"] intValue];
            if (type == formulaOperandOpenParen || type == formulaListOperand || type == formulaOpenParen) [list add:ProcessElementStack(cformula, [dict objectForKey:@"name"])];
            else if (type == formulaField || type == formulaConstant) [list add:NumberForDict(dict)];
            else if (type == formulaOperand) [list add:[dict objectForKey:@"name"]];
            else if (type == formulaCloseParen) {
                if (function){
                    if ([function isEqualToString:@"AVG("]) return Average(list);
                    if ([function isEqualToString:@"MIN("]) return Minimum(list);
                    if ([function isEqualToString:@"MAX("]) return Maximum(list);
                    if ([function isEqualToString:@"SQRT("]) return SquareRoot(list);
                    if ([function isEqualToString:@"ABS("]) return EvaluateStack(list).absoluteValue;
                    return EvaluateStack(list);
                } else break;
            }
        }
        return EvaluateStack(list);
    } copy];
    NSDecimalNumber *number = ProcessElementStack([formula copy], nil); 

UPDATE So in my own research I've discovered that the problem apparently does have to do with the references to the other blocks this block uses. If I do something simple like this, it doesn't leak:

 __block void (^LeakingBlock)(int) = [^(int i){
        i++;
        if (i < 100) LeakingBlock(i);
    } copy];
    LeakingBlock(1);

However, if I add a another block in this, it does leak:

void (^Log)(int) = ^(int i){
   NSLog(@"log sub %i", i);
};

__block void (^LeakingBlock)(int) = [^(int i){
    Log(i);
    i++;
    if (i < 100) LeakingBlock(i);
} copy];
LeakingBlock(1);

I've tried using the __block keyword for Log() and also tried copying it, but it still leaks. Any ideas?

UPDATE 2 I found a way to prevent the leak, but it's a bit onerous. If I convert the passed in block to a weak id, and then cast the weak id back into a the block type, I can prevent the leak.

void (^Log)(int) = ^(int i){
    NSLog(@"log sub %i", i);
};

__weak id WeakLogID = Log;

__block void (^LeakingBlock)(int) = [^(int i){
    void (^WeakLog)(int) = WeakLogID;
    WeakLog(i);
    if (i < 100) LeakingBlock(++i);
} copy];
LeakingBlock(1);

Surely there's a better way?

回答1:

Ok, I found the answer on my own...but thanks to those who tried to help.

If you're referencing/using other blocks in a recursive block, you must pass them in as weak variables. Of course, __weak only applies to block pointer types, so you must typedef them first. Here's the final solution:

    typedef void (^IntBlock)(int);

    IntBlock __weak Log = ^(int i){
        NSLog(@"log sub %i", i);
    };

    __block void (^LeakingBlock)(int) = ^(int i){
        Log(i);
        if (i < 100) LeakingBlock(++i);
    };
    LeakingBlock(1);

The above code doesn't leak.



回答2:

Aaron,

As your code appears to be single threaded, why are you copying the block? If you don't copy the block, you don't have a leak.

Andrew



回答3:

Without further context information, I can say this:

You are leaking that block because you are copying it and not releasing it elsewhere. You need to copy it to move it to the heap, that's ok. But the way you've chosen is not entirely ok.

A correct way to do it is to store it as some object instance variable, copy it, and then release it inside dealloc. At least, that's a way to do it without leaking.