Цикл рекурсии и прерывания блоков
Чтобы лучше проиллюстрировать вопрос, рассмотрим следующую упрощенную форму рекурсии блоков:
__block void (^next)(int) = ^(int index) {
if (index == 3) {
return;
}
int i = index;
next(++i);
};
next(0);
XCode (ARC-enabled) предупреждает, что "Capturing" next "сильно в этом блоке, вероятно, приведет к циклу сохранения".
Согласен.
Вопрос 1. Будет ли успешно сохранен цикл сохранения, установив сам блок на nil
следующим образом:
__block void (^next)(int) = ^(int index) {
if (index == 3) {
next = nil; // break the retain cycle
return;
}
int i = index;
next(++i);
};
next(0);
(Примечание: вы все равно получите такое же предупреждение, но, возможно, оно необоснованное)
Вопрос 2: Какова была бы лучшая реализация рекурсии блоков?
Спасибо.
Ответы
Ответ 1
Чтобы выполнить выполнение рекурсивного блока без сохранения цикла, вам нужно использовать две ссылки блоков - одну слабую и сильную. Итак, для вашего случая это код выглядит так:
__block __weak void (^weak_next)(int);
void (^next)(int);
weak_next = next = ^(int index) {
if (index == 3) {
return;
}
int i = index;
weak_next(++i);
};
next(0);
Обратите внимание, что блок фиксирует ссылку на слабый блок (weak_next), а внешний контекст фиксирует сильную ссылку (далее), чтобы сохранить блок вокруг. Обе ссылки указывают на один и тот же блок.
См. fooobar.com/questions/327664/... для другого примера этого шаблона, который также использует рекурсию блока. Кроме того, здесь обсуждается обсуждение в разделе комментариев следующей статьи: http://ddeville.me/2011/10/recursive-blocks-objc/
Ответ 2
Я думаю, @newacct верен в решении @Matt Wilding; кажется, что в этом случае ничто не будет иметь сильного обращения к следующему блоку и приведет к возникновению исключения времени выполнения при запуске (по крайней мере, для меня).
Я не знаю, насколько распространено найти рекурсивно называемые блоки в дикой природе в objc. Однако в реальном мире (если это действительно необходимо), например, контроллере представления, можно определить блок, а затем настроить свойство внутреннего интерфейса с сильной ссылкой на указанный блок:
typedef void(^PushButtonBlock)();
@interface ViewController ()
@property (strong, nonatomic) PushButtonBlock pushButton;
@end
@implementation ViewController
...
// (in viewDidLoad or some such)
__weak ViewController *weakSelf = self;
self.pushButton = ^() {
[weakSelf.button pushIt];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), weakSelf.pushButton);
};
self.pushButton();
...
@end
Это отлично работает для меня и не имеет предупреждений о компиляторе о циклах сохранения (и никаких утечек в инструментах). Но, я думаю, я бы, вероятно, избегал этого (рекурсивных вызовов блоков) в большинстве случаев в objc - это вонючий. Но интересно в любом случае.