Рекурсивные блоки в Objective-C, протекающие в ARC
Итак, я использую рекурсивные блоки. Я понимаю, что для того, чтобы блок был рекурсивным, ему должно предшествовать ключевое слово __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);
Конечно, лучший способ?
Ответы
Ответ 1
Хорошо, я нашел ответ самостоятельно... но благодаря тем, кто пытался помочь.
Если вы ссылаетесь/используете другие блоки в рекурсивном блоке, вы должны передать их в качестве слабых переменных. Конечно, __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);
Приведенный выше код не протекает.
Ответ 2
Аарон
Как ваш код выглядит однопоточным, почему вы копируете блок? Если вы не скопируете блок, у вас нет утечки.
Эндрю
Ответ 3
Без дополнительной информации о контексте я могу сказать следующее:
Вы просачиваете этот блок, потому что копируете его и не выпускаете его в другом месте. Вам нужно скопировать его, чтобы переместить его в кучу, это нормально. Но способ, который вы выбрали, не совсем нормально.
Правильный способ сделать это - сохранить его как некоторую переменную экземпляра объекта, скопировать ее и затем отпустить в dealloc. По крайней мере, это способ сделать это без утечки.