Ссылка на объект NSOperation в своем собственном блоке завершения с ARC - PullRequest
12 голосов
/ 10 февраля 2012

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

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

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" );

        ...
    });
}];

Ответы [ 3 ]

16 голосов
/ 13 февраля 2013

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

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 см. Последний фрагмент кода в Использование классификаторов продолжительности жизни для избежания сильных циклов ссылок .

10 голосов
/ 10 февраля 2012

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

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

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;
    });

}];
4 голосов
/ 13 ноября 2014

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

цитата из NSOperation документация на @ завершениеБлок :

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

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

...