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

Я пытаюсь закончить головоломку.

__strong является значением по умолчанию для всех сохраняемых указателей объектов Objective-C, таких как NSObject, NSString и т.д. Это сильная ссылка. ARC балансирует его с помощью -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

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

__ 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, напрямую.

Ответ 2

Следуя ответам Macmade и Вопросам правдома в комментариях, (также разместил бы это как комментарий, но он превысил максимальное количество символов):

Вот почему разделитель переменных __autoreleasing помещается между двумя звездами.

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

NSError * __qualifier someError;

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

__qualifier NSError *someError;

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

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

SomeClass * __qualifier *someVariable;

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

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

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

Ответ 3

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

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

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

NSError* __autoreleasing error = someError;

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

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

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

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