Блоки iOS и сильные/слабые ссылки на себя

У меня вопрос о сильных и слабых ссылках на себя в блоках в iOS. Я знаю, что правильный способ ссылаться на себя внутри блока - создать слабую ссылку вне блока, а затем сильную ссылку на эту слабую ссылку внутри блока, например:

__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^ {
    typeof(self) strongSelf = weakSelf;
    NSLog(@"%@", strongSelf.someProperty);
});

Однако, что произойдет, если у вас есть вложенные блоки? Достаточно ли одного набора ссылок? Или вам нужен новый набор для каждого блока? Например, какое из следующего верно?

Это:

__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^ {
    typeof(self) strongSelf = weakSelf;
    NSLog(@"%@", strongSelf.someProperty);
    dispatch_async(dispatch_get_main_queue(), ^ {
        strongSelf.view.frame = CGRectZero;
    });
});

Или это:

__weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^ {
        typeof(self) strongSelf = weakSelf;
        NSLog(@"%@", strongSelf.someProperty);
        __weak typeof(strongSelf) weakSelf1 = strongSelf;
        dispatch_async(dispatch_get_main_queue(), ^ {
            typeof(strongSelf) strongSelf1 = weakSelf1;
            strongSelf1.view.frame = CGRectZero;
        });
    });

Любая информация или объяснение очень ценится!

Ответ 1

Вам не нужно делать два набора слабых ссылок. То, что вы хотите избежать с помощью блоков, - это цикл сохранения - два объекта, которые ожидают друг друга без необходимости.

Если у меня есть объект с этим свойством:

@property (strong) void(^completionBlock)(void);

и у меня есть этот метод:

- (void)doSomething
{
    self.completionBlock = ^{
        [self cleanUp];
    };

    [self doLongRunningTask];
}

блок будет сохранен в живых, когда я сохраню его в свойстве completionBlock. Но поскольку он ссылается на self внутри блока, блок будет поддерживать self до тех пор, пока он не исчезнет, ​​но это не произойдет, поскольку они ссылаются друг на друга.

В этом методе:

- (void)doSomething
{
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        [self cleanUp];
    }];

    [self doLongRunningTask];
}

вам не нужно делать слабую ссылку на self. Блок будет поддерживать self вживую, так как он ссылается на self изнутри, но поскольку все они делали, это отведение блока до [NSOperationQueue mainQueue], self не удерживает блок в живых.

Надеюсь, что это поможет.

Ответ 2

Обе конструкции в порядке. Это зависит только от ваших намерений. Что вы хотите сделать, если объект (a) выпущен после того, как внешний блок начнется, но (b), прежде чем внутренний блок начнет работу в главной очереди? Если вы не хотите, чтобы он сохранялся в этом сценарии (что, я думаю, было вашим намерением, учитывая, что вы проходите это упражнение weakSelf в первую очередь), затем используйте свой последний пример, где у вас есть второй слабый указатель, В противном случае вы можете использовать другой пример.

Сказав это, несколько замечаний:

  • Это не лишний вывод о том, что вы должны использовать этот шаблон weakSelf в первую очередь. Некоторые люди ошибочно полагают, что они должны использовать это weakSelf шаблон, чтобы избежать сильного опорного цикла (а.к.а. сохранить цикл). Но этот образец кода не является сильным опорным циклом. Он просто сохраняет объект во время выполнения отправленного кода, что является совсем другим соображением.

    Фактически, иногда вам это нужно/нужно. Иногда вы этого не делаете. Это зависит от бизнес-проблемы, которую вы решаете. Абсолютно, вы часто не хотите, чтобы он содержал сильную ссылку на self, и в этом случае шаблон weakSelf имеет смысл. Но это не всегда так.

    Но я хочу сказать, что вы не должны преследовать этот шаблон weakSelf (по крайней мере, в этом сценарии dispatch_async), чтобы избежать сильного эталонного цикла. Такой цикл не существует. Там, где это проблема, у вас есть блок-переменная (например, некоторый блок completionHandler). В этом случае шаблон weakSelf имеет решающее значение. Но не здесь.

  • Но рассмотрим на секунду тот сценарий, в котором вы не хотите, чтобы self сохранялся. Тогда возникает вопрос, хотите ли вы, чтобы отправленный код продолжался вообще в первую очередь. Если нет, возможно, вы должны использовать операционную очередь с отменными операциями вместо GCD.

    Например, я удивляюсь, как часто люди мучаются за то, будут ли они сохранять контроллер вида, пока выполняется какой-либо фоновый сетевой запрос, но не беспокойтесь о том, следует ли отменить этот фоновый сетевой запрос в первое место. Часто последнее является гораздо более важным соображением дизайна (например, PDF или изображение, которое вы загружаете, занимают гораздо больше системных ресурсов (как памяти, так и пропускной способности сети), чем когда-либо будет контроллер просмотра).

  • Но допустим, что (а) вы действительно хотите, чтобы отправленный код продолжал выполняться, но (b) вы не хотите сохранять self. (Это похоже на редкий сценарий, но это тот, о котором вы просили, поэтому давайте продолжать это.) Последний вопрос о том, нужна ли вам ваша конструкция strongSelf. В вашем случае, когда вы просто вызываете единственный метод self, вам не нужно беспокоиться об этой конструкции strongSelf. Это важно только в том случае, если вы собираетесь уважать иваров или иначе должны избегать условий гонки. Но в этом примере, учитывая, что сообщение, отправленное объекту nil, ничего не делает, вам технически часто не нужно беспокоиться об этой конструкции strongSelf вообще.

Не поймите меня неправильно. Хорошо иметь одно оружие вокруг шаблона weakSelf, а также вложенный шаблон strongSelf, который иногда сопровождает его. Я просто предлагаю вам понять, когда эти шаблоны действительно необходимы. И я думаю, что выбор GCD по сравнению с отменным NSOperation часто является гораздо более критичным, но часто упускаемым вопросом.

Ответ 3

Блоки создаются и сохраняются в стеке. Таким образом, блок будет уничтожен, когда метод, который создал блок, вернется.

Если блок становится переменной экземпляра, ARC копирует блок из стека в кучу. Вы можете явно скопировать блок с копированием. Теперь ваш блок представляет собой блок на основе кучи вместо блока на основе стека. И вам приходится иметь дело с некоторыми проблемами управления памятью. Сам блок будет содержать ссылки на любые объекты, на которые он ссылается. Объявите указатели __weak за пределами блока, а затем ссылайтесь на этот указатель внутри блока, чтобы избежать циклов сохранения.