В каких ситуациях нам нужно написать квалификатор владения __autoreleasing под ARC?

118

Я пытаюсь решить головоломку.

__strongявляется значением по умолчанию для всех сохраняемых указателей объектов Objective-C, таких как NSObject, NSString и т. д. Это сильная ссылка. ARC уравновешивает это с помощью a -releaseв конце области видимости.

__unsafe_unretainedравно старому. Он используется для слабого указателя без сохранения сохраняемого объекта.

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

Но что именно __autoreleasingхорошо? Мне сложно найти практические примеры, когда мне нужно использовать этот квалификатор. Я считаю, что это только для функций и методов, которые ожидают указатель-указатель, например:

- (BOOL)save:(NSError**);

или

NSError *error = nil;
[database save:&error];

который под ARC должен быть объявлен следующим образом:

- (BOOL)save:(NSError* __autoreleasing *);

Но это слишком расплывчато, и я хотел бы полностью понять, почему . Во фрагментах кода, которые я нахожу, __autoreleasing помещается между двумя звездами, что мне кажется странным. Типом является NSError**(указатель-указатель на NSError), так почему же ставить __autoreleasingмежду звездами, а не просто перед ними NSError**?

Кроме того, могут быть и другие ситуации, на которые я должен положиться __autoreleasing.

Гордый член
источник
1
У меня есть тот же вопрос, и ответы ниже не совсем убедительны ... например, почему система не предоставляет интерфейсы, которые принимают аргументы NSError **, объявленные с помощью декоратора __autoreleasing, как вы, и примечания к выпуску Transitioning to Arc говорят, что они должно быть? например, Любая из многих из этих подпрограмм в NSFileManager.h ??
Папа

Ответы:

67

Ты прав. Как объясняется в официальной документации:

__autoreleasing для обозначения аргументов, которые передаются по ссылке (id *) и автоматически освобождаются при возврате.

Все это очень хорошо объясняется в руководстве по переходу ARC .

В вашем примере NSError объявление __strongнеявно означает :

NSError * e = nil;

Преобразуется в:

NSError * __strong error = nil;

Когда вы вызываете свой saveметод:

- ( BOOL )save: ( NSError * __autoreleasing * );

Затем компилятору нужно будет создать временную переменную, установленную в __autoreleasing. Так:

NSError * error = nil;
[ database save: &error ];

Преобразуется в:

NSError * __strong error = nil;
NSError * __autoreleasing tmpError = error;
[ database save: &tmpError ];
error = tmpError;

Вы можете избежать этого __autoreleasing, напрямую объявив объект ошибки как .

Macmade
источник
3
Нет, __autoreleasingиспользуется только для аргументов, переданных по ссылке. Это особый случай, поскольку у вас есть указатель на указатель объекта. Это не относится к таким вещам, как удобные конструкторы, поскольку они возвращают только указатель на объект, а ARC обрабатывает его автоматически.
Macmade
7
Почему квалификатор __autoreleasing помещается между звездочками, а не только перед NSError **? Мне это кажется странным, поскольку тип - NSError **. Или это потому, что это пытается указать, что указатель NSError * должен быть квалифицирован как указывающий на автоматически выпущенный объект?
Гордый участник
1
@Proud Member в отношении вашего первого комментария - это неверно (если я правильно вас понял) - см. Ответ Глена Лоу ниже. Объект ошибки создается и назначается автоматически освобождающейся переменной (той, которую вы передали) внутри функции сохранения. Это назначение вызывает сохранение и автоматическое освобождение объекта в это время. Объявление функции сохранения не позволяет нам отправлять ей что-либо, кроме переменной с автоматическим освобождением, потому что это именно то, что ему нужно - вот почему компилятор создает временную переменную, если мы попытаемся.
Colin
2
Так почему же, похоже, этого нет ни в одном из интерфейсов Apple? например, все в NSFileManager.h?
Папа
1
@Macmade: Просто случайно я заметил, что ваш ответ был отредактирован ( stackoverflow.com/users/12652/comptrol ), и у меня сложилось впечатление, что по крайней мере изменения в вашем первом примере ("неявно ... будут преобразованы в ...) ошибочны, потому что квалификатор __strong был перемещен со второй строки на первую строку. Возможно, вы могли бы это проверить.
Мартин Р.
34

Следуя ответу Макмаде и последующему вопросу Гордого члена в комментариях, (также разместил бы это как комментарий, но он превышает максимальное количество символов):

Вот почему квалификатор переменной __autoreleasing помещен между двумя звездами.

Чтобы предисловие, правильный синтаксис для объявления указателя на объект с квалификатором:

NSError * __qualifier someError;

Компилятор простит это:

__qualifier NSError *someError;

но это неверно. См. Руководство по переходу Apple ARC (прочтите раздел, который начинается с «Правильно декорировать переменные ...»).

Чтобы обратиться к рассматриваемому вопросу: двойной указатель не может иметь квалификатор управления памятью ARC, потому что указатель, указывающий на адрес памяти, является указателем на примитивный тип, а не указателем на объект. Однако, когда вы объявляете двойной указатель, ARC действительно хочет знать, каковы правила управления памятью для второго указателя. Вот почему переменные с двойным указателем указываются как:

SomeClass * __qualifier *someVariable;

Таким образом, в случае аргумента метода, который является двойным указателем NSError, тип данных объявляется как:

- (BOOL)save:(NSError* __autoreleasing *)errorPointer;

который на английском языке говорит «указатель на указатель объекта __autoreleasing NSError».

Биньямин Бауман
источник
15

В окончательной спецификации ARC говорится, что

Для объектов __autoreleasing новый указатель сохраняется, автоматически освобождается и сохраняется в lvalue с использованием примитивной семантики.

Так, например, код

NSError* __autoreleasing error = someError;

фактически преобразуется в

NSError* error = [[someError retain] autorelease];

... поэтому он работает, когда у вас есть параметр NSError* __autoreleasing * errorPointer, вызываемый метод затем назначит ошибку, *errorPointerи вышеуказанная семантика сработает.

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

Глен Лоу
источник