Что такое предупреждение Apple в документации ARC, оставьте ссылку на ссылку?

В документации Apple о ARC они создают проблему с описанием проблемного сценария, в котором ARC генерирует временную переменную шаблона за кулисами. Поиск в "Компилятор поэтому перезаписывает":

https://developer.apple.com/library/mac/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html

Суть предупреждения заключается в том, что, поскольку переменная, основанная на стеке, является "сильной", а параметр by-reference вызываемому методу (performOperationWithError:) является автореализацией, ARC будет генерировать временную локальную переменную для обслуживания памяти потребности управления автореализационной переменной. Но поскольку временная переменная присваивается сильной переменной в примере шаблона, кажется, что с клиентской точки зрения нет риска.

В чем именно документация борется, чтобы предупредить нас здесь? В чем заключается риск как клиента или как исполнителя метода, который может быть вызван таким образом (с параметром autoreleased, return-by-value)?

Ответ 1

Это только предупреждение о менее идеальной производительности. В перезаписанном коде NSError, на который указывает "tmp", возвращается автореализован, сохраняется при назначении "ошибка", а затем снова освобождается, когда "ошибка" выходит за пределы области видимости.

Если вы измените объявление в исходном коде на: Ошибка NSError __autoreleasing *;

Если вы сделаете это, не будет назначений на temp, и это неявное сохранение, а затем освобождение больше не произойдет. (Сам объект NSError по-прежнему действует до тех пор, пока он был до этого, поскольку он все еще находится в пуле автозапуска). Поэтому в документации предупреждается, что если вы используете "неправильный" классификатор переменных, это может привести к увеличению количества накоплений которые в противном случае не требовались бы.

Также обратите внимание, что с любой версией кода: поскольку переменная, о которой идет речь, передается по ссылке и не является возвращаемым значением из -performOperationWithError:, нет возможности сделать волшебный стековый ходячий трюк, который ARC может сделайте, чтобы сохранить объект от входа в пул автозапуска в первую очередь.

Ответ 2

Я думаю, что это предотвратит путаницу, если вы начнете смотреть на значения, переданные в этот метод. В их примере, если я установил точку останова на строке, которая вызывает [myObject performOperationWithError:&tmp]; и наберите p error, я увижу адрес ее. Но если я вхожу в -performOperationWithError: и набираю p error, я получаю другое значение внутри метода, error указывает на это временное значение.

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

Ответ 3

Мое предположение: если вы сделали предположения о памяти, на которую ссылается выходной параметр, например, индексируя указатель, вы можете быть удивлены.

Ответ 4

Я не думаю, что это имеет какое-то отношение к клиенту. Это похоже на ссылку на тот же самый вопрос, который был рассмотрен в видеоролике WWDC 2013 по проблемам памяти: если вы сами реализуете метод, который принимает параметр автореализационной индикации (например, NSError**), и если вы создаете блок пула автозаполнения внутри этого метод, не назначайте NSError изнутри блока пула автозаполнения. Вместо этого назначьте локальную переменную, а затем назначьте из локального NSError вне блока пула автозапуска.

Ответ 5

Мне кажется, что это меньше предупреждений об этом поведении, чем описание того, что делает компилятор в этом случае, и почему вы можете передать адрес сильной локальной ссылки на ошибку методу, объявленному как желающий ссылку __autoreleasing и не вызывать жалобы.

Обычно вы хотите, чтобы API использовал __autoreleasing по такому параметру в случае, если он используется кодом ARC или не ARC, как и в коде, отличном от ARC, было бы необычным, чтобы выпустить такой выходной параметр.

Ответ 6

Документация Apple ссылается на компилятор misfeature, который будет синтезировать временную переменную для вас, чтобы иметь дело с преобразованием между __block и __autoreleasing. К сожалению, это не решает очень много проблем и создает потенциально непредвиденные неожиданные результаты.

Например:

int main(int argc, char *argv[])
{
    __block id value = @"initial value";
    void (^block)(id *outValue) = ^(id *outValue){
        value = @"hello";
    };
    block(&value);
    NSLog(@"value = %@", value);
    return 0;
}

С ARC это сообщает:

2013-04-24 13:55:35.814 block-local-address[28013:707] value = initial value

но с MRR:

2013-04-24 13:57:26.058 block-local-address[28046:707] value = hello

Это очень часто возникает при использовании NSFileCoordinator, заставляя вас потерять полученный NSError!

#import <Foundation/Foundation.h>

int main(int argc, char *argv[])
{
    NSURL *fileURL = [NSURL fileURLWithPath:@"/tmp/foo"];
    NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];

    __block NSError *error;
    [coordinator coordinateWritingItemAtURL:fileURL options:0 error:&error byAccessor:^(NSURL *newURL){
        NSDictionary *userInfo = @{
            NSLocalizedDescriptionKey : @"Testing bubbling an error out from a file coordination block."
        };
        error = [NSError errorWithDomain:NSPOSIXErrorDomain code:ENOSYS userInfo:userInfo];
    }];

    NSLog(@"error = %@", error);
}

При компиляции с ARC это приводит к ошибке nil!

Это было написано как ошибка на llvm.org некоторое время, хотя я просто изменил заголовок, чтобы было более ясно, что Я предлагаю, чтобы эта функция была сорвана. Также к этой ошибке добавляется патч для добавления нового флага -fno-objc-arc-writeback, чтобы отключить эту функцию).