Подтвердить что ты не робот

Рекурсивные блоки сохранения блоков

Это приведет к какому-либо циклу удержания? Безопасно ли использовать?

__block void (^myBlock)(int) = [^void (int i)
{
    if (i == 0)
        return;

    NSLog(@"%d", i);
    myBlock(i - 1);
} copy];
myBlock(10);

myBlock = nil;
4b9b3361

Ответ 1

В коде есть содержится цикл сохранения, но вы можете разбить цикл сохранения в конце рекурсии, установив myBlock на нуль в случае базы рекурсии (i == 0).

Лучший способ доказать это - попробовать его, работая под инструментом Allocations, с выключенным "Отменить незаписанные данные при остановке", "Включить учетные записи", отключить "Только отслеживать активные выделения".

Я создал новый проект Xcode с использованием шаблона инструмента командной строки OS X. Здесь вся программа:

#import <Foundation/Foundation.h>

void test() {
    __block void (^myBlock)(int) = [^void (int i){
        if (i == 0) {
//            myBlock = nil;
            return;
        }
        NSLog(@"myBlock=%p %d", myBlock, i);
        myBlock(i - 1);
    } copy];
    myBlock(10);
}

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        test();
    }
    sleep(1);
    return 0;
}

Затем я запустил его под инструментом Allocations с настройками, описанными выше. Затем я изменил "Статистика" на "Консоль" в "Инструменты", чтобы увидеть выход программы:

2012-10-26 12:04:31.391 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 10
2012-10-26 12:04:31.395 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 9
2012-10-26 12:04:31.396 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 8
2012-10-26 12:04:31.397 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 7
2012-10-26 12:04:31.397 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 6
2012-10-26 12:04:31.398 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 5
2012-10-26 12:04:31.398 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 4
2012-10-26 12:04:31.399 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 3
2012-10-26 12:04:31.400 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 2
2012-10-26 12:04:31.401 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 1
<End of Run>

Я скопировал адрес блока (0x7ff142c24700), изменил "Консоль" на "Список объектов" и вставил адрес в поле поиска. Инструменты показали мне только выделение для блока:

block leaked

Точка под столбцом Live означает, что блок все еще был выделен при выходе из программы. Он просочился. Я щелкнул стрелку рядом с адресом, чтобы увидеть полную историю выделения блока:

block leaked detail

Только одно событие с этим распределением: оно было выделено.

Затем я раскомментировал строку myBlock = nil в инструкции if (i == 0). Затем я снова запустил его под профилировщиком. Система рандомизирует адреса памяти для обеспечения безопасности, поэтому я очистил панель поиска, а затем снова проверил консоль для адреса блока в этом прогоне. На этот раз это было 0x7fc7a1424700. Я снова переключился на представление "Список объектов" и вставил его в новый адрес 0x7fc7a1424700. Вот что я увидел:

block freed

На этот раз нет столбцов под столбцом Live, что означает, что к моменту выхода программы блок был освобожден. Затем я нажал на стрелку рядом с адресом, чтобы увидеть полную историю:

block freed detail

На этот раз блок был выделен, выпущен и освобожден.

Ответ 2

Существует простое решение, которое позволяет избежать цикла и потенциальной необходимости преждевременной копии:

void (^myBlock)(id,int) = ^(id thisblock, int i) {
    if (i == 0)
      return;

    NSLog(@"%d", i);
    void(^block)(id,int) = thisblock;
    block(thisblock, i - 1);
  };

myBlock(myBlock, 10);

Вы можете добавить обертку, чтобы вернуть исходную подпись типа:

void (^myBlockWrapper)(int) = ^(int i){ return myBlock(myBlock,i); }

myBlockWrapper(10);

Это становится утомительным, если вы хотите расширить его, чтобы сделать взаимную рекурсию, но я не могу придумать вескую причину, чтобы сделать это в первую очередь (не был бы класс более ясным?).

Ответ 3

Если вы используете ARC, у вас есть цикл сохранения, потому что __block переменные объекта сохраняются блоком. Таким образом, блок сохраняет себя. Вы можете избежать этого, объявив myBlock как __block и __weak.

Если вы используете MRC, переменные объекта __block не сохраняются, и у вас не должно быть проблем. Просто не забудьте освободить myBlock в конце.

Ответ 4

Нет, это не вызовет цикл сохранения. Ключевое слово __block указывает блоку не копировать myBlock, который произошел до назначения, вызвавшего сбой приложения. Если это не ARC, единственное, что вам нужно сделать, это освободить myBlock после вызова myBlock(10).

Ответ 5

Мне нужно решение, которое не получает никаких предупреждений, и в этом потоке fooobar.com/questions/260373/... Tammo Freese дает лучшее решение:

__block void (__weak ^blockSelf)(void);
void (^block)(void) = [^{
        // Use blockSelf here
} copy];
blockSelf = block;
    // Use block here

Его объяснение имеет смысл.

Ответ 6

Вот современное решение проблемы:

void (^myBlock)();
__block __weak typeof(myBlock) weakMyBlock;
weakMyBlock = myBlock = ^void(int i) {
    void (^strongMyBlock)() = weakMyBlock; // prevents the block being delloced after this line. If we were only using it on the first line then we could just use the weakMyBlock.
    if (i == 0)
        return;

    NSLog(@"%d", i);
    strongMyBlock(i - 1);
};
myBlock(10);