Ссылка на объект NSOperation в своем собственном блоке завершения с ARC

Мне сложно преобразовать код NSOperation в ARC. Мой объект операции использует блок завершения, который, в свою очередь, содержит блок GCD, который обновляет пользовательский интерфейс в основном потоке. Поскольку я ссылаюсь на свой рабочий объект изнутри собственного блока завершения, я использую указатель __weak, чтобы избежать утечки памяти. Однако указатель уже установлен на нуль к моменту моего кода.

Я сузил его до этого образца кода. Кто-нибудь знает, где я поступил не так, и верный способ выполнить это?

NSOperationSubclass *operation = [[NSOperationSubclass alloc] init];
__weak NSOperationSubclass *weakOperation = operation;

[operation setCompletionBlock:^{
    dispatch_async( dispatch_get_main_queue(), ^{

        // fails the check
        NSAssert( weakOperation != nil, @"pointer is nil" );

        ...
    });
}];

Ответ 1

Я не уверен в этом, но правильный способ сделать это, возможно, добавить __block к рассматриваемой переменной, а затем установить его в конец в конце блока, чтобы убедиться, что он выпущен. См. этот вопрос.

Ваш новый код будет выглядеть так:

NSOperationSubclass *operation = [[NSOperationSubclass alloc] init];
__block NSOperationSubclass *weakOperation = operation;

[operation setCompletionBlock:^{
    dispatch_async( dispatch_get_main_queue(), ^{

        // fails the check
        NSAssert( weakOperation != nil, @"pointer is nil" );

        ...
        weakOperation = nil;
    });

}];

Ответ 2

Другой вариант:

NSOperationSubclass *operation = [[NSOperationSubclass alloc] init];
__weak NSOperationSubclass *weakOperation = operation;

[operation setCompletionBlock:^{
    NSOperationSubclass *strongOperation = weakOperation;

    dispatch_async(dispatch_get_main_queue(), ^{
        assert(strongOperation != nil);
        ...
    });
}];

[operationQueue addOperation:operation];

Я предполагаю, что вы также добавите объект операции в NSOperationQueue. В этом случае очередь сохраняет операцию. Вероятно, это также сохраняется во время выполнения блока завершения (хотя я не нашел официального подтверждения о блоке завершения).

Но внутри блока завершения создается еще один блок. Этот блок будет запущен в какой-то момент позже, возможно, после завершения блока завершения NSOperations. Когда это произойдет, operation будет освобожден в очереди, а weakOperation будет nil. Но если мы создадим еще одну сильную ссылку на один и тот же объект из блока завершения операции, мы убедимся, что operation будет существовать при запуске второго блока и избежать цикла сохранения, потому что мы не фиксируем переменную operation блоком.

Apple предоставляет этот пример в Переход к заметкам о выпуске ARC, см. последний фрагмент кода в разделе "Использовать пожизненные классификаторы для исключения сильных ссылочных циклов".

Ответ 3

Принятый ответ правильный. Однако нет необходимости ослаблять работу с iOS 8/Mac OS 10.10:

цитата из Документация NSOperation на @completionBlock:

В iOS 8 и более поздних версиях и OS X v10.10 и более поздних версиях это свойство устанавливается равным nil после начала выполнения блока завершения.

См. также этот твит от Пете Штайнбергера.