Блоки 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;
        });
    });

Любая информация или объяснения высоко ценится!

3 ответа

Решение

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

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

@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 не держит блок в живых.

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

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

Сказав это, пара замечаний:

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам