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

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

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

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

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

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

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

или же

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

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

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

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

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

4 ответа

Решение

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

__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 и последующему вопросу гордого участника в комментариях (также разместил бы это как комментарий, но он превышает максимальное количество символов):

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

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

NSError * __qualifier someError;

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

__qualifier NSError *someError;

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

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

SomeClass * __qualifier *someVariable;

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

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

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

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

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

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

NSError* __autoreleasing error = someError;

на самом деле превращается в

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

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

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

Короче говоря: это только для совместимости с MRC.

Apple договорились, что в собственных библиотеках объекты, возвращаемые **всегда выпускаются автоматически. Таким образом, код будет нормально работать со старыми двоичными файлами (например, если у вас есть цель развертывания iOS 4) и наоборот. MRCкод будет работать нормально с ARCдвоичные файлы.

Итак, в заключение:

  • Вы никогда не должны использовать __autoreleasing: компилятор автоматически добавит его там, где это необходимо

  • Если вы не собираетесь поддерживать код MRC, вам следует использовать * __strong *везде. Это убережет от краха семьи:

            @autoreleasingpool {
       *autorelesingOut = [@"crash maker" mutableCopy];//NSString * __autoreleasing *autorelesingOut;
       *strongOut = [@"it's ok" mutableCopy];//NSString * __strong *strongOut;
       //App will crash if autorelesingOut will be referenced outside of this autoreleasepool
    }
    
Другие вопросы по тегам