Рекурсивные блоки в Objective-C просачиваются в ARC - PullRequest
15 голосов
/ 14 января 2012

Так что я использую рекурсивные блоки. Я понимаю, что для того, чтобы блок был рекурсивным, ему должно предшествовать ключевое слово __block, и его нужно скопировать, чтобы его можно было поместить в кучу. Однако, когда я делаю это, это обнаруживается как утечка в Инструментах. Кто-нибудь знает, почему или как я могу обойти это?

Обратите внимание, что в приведенном ниже коде у меня есть ссылки на множество других блоков, но ни один из них не является рекурсивным.

__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 Итак, в своем собственном исследовании я обнаружил, что проблема, очевидно, имеет отношение к ссылкам на другие блоки, которые использует этот блок. Если я сделаю что-то простое, это не произойдет:

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

Однако, если я добавлю еще один блок в этом, это действительно утечка:

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

Я пытался использовать ключевое слово __block для Log (), а также пытался скопировать его, но оно все еще протекает. Есть идеи?

ОБНОВЛЕНИЕ 2 Я нашел способ предотвратить утечку, но это немного обременительно. Если я преобразую переданный блок в слабый идентификатор, а затем приведу слабый идентификатор обратно в тип блока, я смогу предотвратить утечку.

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

Конечно, есть лучший способ?

Ответы [ 3 ]

11 голосов
/ 17 января 2012

Хорошо, я нашел ответ самостоятельно ... но благодаря тем, кто пытался помочь.

Если вы ссылаетесь / используете другие блоки в рекурсивном блоке, вы должны передавать их как слабые переменные. Конечно, __weak применяется только к типам указателей блоков, поэтому вы должны сначала определить их. Вот окончательное решение:

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

Приведенный выше код не протекает.

0 голосов
/ 17 января 2012

Аарон,

Поскольку ваш код кажется однопоточным, почему вы копируете блок?Если вы не скопируете блок, у вас нет утечки.

Андрей

0 голосов
/ 16 января 2012

Без дополнительной контекстной информации я могу сказать следующее:

Вы пропускаете этот блок, потому что копируете его, а не выпускаете в другом месте. Вам нужно скопировать его, чтобы переместить в кучу, это нормально. Но выбранный вами способ не совсем в порядке.

Правильный способ сделать это - сохранить его как некоторую переменную экземпляра объекта, скопировать и затем освободить его внутри dealloc. По крайней мере, это способ сделать это без утечки.

...