Когда требуется NS_RETURNS_RETAINED?

Возьмите приведенный ниже пример:

- (NSString *)pcen NS_RETURNS_RETAINED {
    return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef) self, NULL, (CFStringRef) @"!*'();:@&=+$,/?%#[]", kCFStringEncodingUTF8);
}

Правильно ли там поставить NS_RETURNS_RETAINED?


Другой пример:

+ (UIImage *)resizeImage:(UIImage *)img toSize:(CGSize)size NS_RETURNS_RETAINED {
    UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
    [img drawInRect:...];
    UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return resizedImage;
}

Это кажется более сложным, так как возвращаемый UIImage является результатом метода Get. Тем не менее, контекст графики, из которого он был получен, был создан в рамках метода, так же верно и здесь NS_RETURNS_RETAINED?


И третий пример:

@property (readonly) NSArray *places;
---
@synthesize places=_places;
---
- (NSArray *)places {
    if (_places)
        return _places;
    return [[NSArray alloc] initWithObjects:@"Unknown", nil];
}

Не знаю, что делать здесь, поскольку возвращаемый объект может быть вновь создан или нет.


И последний вопрос; предположительно NS_RETURNS_RETAINED не требуется, если возвращенный объект является результатом метода autorelease'ed. Поэтому скажем, что возврат в последнем примере был изменен на

return [NSArray arrayWithObject:@"Unknown"];

что было бы лучше всего?

Ответ 1

Первый пример

Правильно ли там поставить NS_RETURNS_RETAINED?

Это неверно - здесь нет атрибута. Добавление атрибута будет противоречить соглашениям об именах, которые очень важны.

Более подробно атрибут не требуется, поскольку ссылка передается в примере с помощью (__bridge_transfer NSString*). Можно предположить, что для CFCreated-Reference может понадобиться нечто большее, но (__bridge_transfer NSString*) - это все, что необходимо для передачи этой ссылки в ARC; для того, чтобы он мог справиться с вами.

Если вы должны были использовать typging с помощью (__bridge NSString*)CF_*_Create_*_, тогда ссылка, возвращаемая функцией CFCreate, не будет передана в ARC, и будет введена утечка.

(В качестве альтернативы эту утечку можно избежать, если вы решили явно освободить возвращаемую строку (например, используя CFRelease).)

Второй пример

Однако графический контекст, из которого он был получен, был создан в рамках метода, так же верно и здесь NS_RETURNS_RETAINED?

Неправильно или необходимо использовать атрибут. ARC использует соглашения об именах и атрибуты для определения операций подсчета ссылок для добавления - он понимает вашу программу.

В отличие от первого примера, явное __bridge_transfer не должно быть сделано.

Добавление атрибута приведет к нарушению соглашений об именах.

Третий пример

- (NSArray *)places 
...

Не знаю, что делать здесь, поскольку возвращаемый объект может быть вновь создан или нет.

Опять же, никакой атрибут не должен использоваться. Явное __bridge_transfer не должно быть сделано. ARC понимает соглашения ObjC, включая возврат существующих и вновь созданных объектов. Он будет вставлять правильные операции подсчета ссылок для обоих путей.

И последний вопрос; предположительно NS_RETURNS_RETAINED не требуется, если возвращенный объект является результатом метода autorelease'ed. Поэтому скажем, что возврат в последнем примере был изменен на

return [NSArray arrayWithObject:@"Unknown"];

Опять же, никакого атрибута не требуется. Явная передача не должна производиться.

Для всех системных библиотек существует только несколько применений атрибута.


Я действительно, действительно, действительно рекомендую не использовать эти атрибуты, в частности:

  • где задействована динамическая отправка (что все методы objc будут квалифицироваться как)
  • где параметры (потребление) и результаты (возвращенные значения) являются типами ObjC

Обоснование заключается в том, что ссылки должны быть локальными для реализаций, и редко существует настоятельная необходимость отклоняться от этого; обратная совместимость, вероятно, является "лучшей" причиной, о которой я могу думать. Если у вас есть контроль над своим кодом, просто обновите его, чтобы делать правильные вещи везде, где это возможно, а не вводить эти атрибуты. Это может быть достигнуто путем соблюдения соглашений об именах и путем передачи ссылок на ARC, где это необходимо.

Для некоторых примеров ошибок вы можете использовать атрибуты и отклоняться от соглашений об именах: Глубокая копия словарей дает ошибку анализа в Xcode 4.2.

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

где-

- (NSString *)name NS_RETURNS_RETAINED;

в других местах

NSString * name = obj.name;
NSLog(@"%@", name);
[name release]; // << ME: not a mistake. triple checked.

Ответ 2

[Этот ответ частично длинный комментарий/исправление ответа Джастина. Этот предыдущий ответ дает я считаю неправильное описание семантики как атрибута, так и того, как ARC обрабатывает возвращаемые ссылки.]

Ответ заключается в том, как работает анализ ARC и значение NS_RETURNS_RETAINED.

ARC анализирует ваш источник, чтобы определить, когда сохранить, освободить или сохранить резервные ссылки на объекты.

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

Однако все источники недоступны - например, некоторые из них уже скомпилированы в рамках и т.д. - поэтому при анализе метода вызов ARC не смотрит на источник метода, а только на его подпись - его имя и типы его параметров и возвращаемое значение.

Учитывая только возвращаемое значение типа сохраняемого объекта, ARC должна знать, передается ли собственность - в этом случае ARC нужно будет выпустить его в какой-то момент - или нет (например, автореализованная ссылка) - в этом случае ARC будет необходимо сохранить его, если требуется владение.

ARC определяет эту информацию на основе имени метода и любых атрибутов. Методы, начинающиеся с init или new или содержащие copy передачу, по определению, собственности; все другие методы этого не делают. Атрибут NS_RETURNS_RETAINED сообщает ARC, что метод, независимо от его имени, передает право собственности на свою возвращенную ссылку.

Это половина истории... вторая половина - это то, как ARC обрабатывает оператор return в теле метода.

A return действительно является типом присвоения, а при выполнении резервирования ссылочной позиции объекта ARC определяет, следует ли сохранять ссылку, автореализоваться или лежать, поскольку она основана на ее знаниях о текущей собственности и ссылке и требования пункта назначения.

Для оператора return требования адресата, неудивительно, определяются именем метода и любыми атрибутами, указанными в подписи. Если подпись указывает, что собственность передается, ARC вернет сохраненную ссылку, иначе она вернет автореализованный файл.

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

Со всей этой преамбулой мы можем посмотреть на ваш первый пример. Похоже, вы пишете метод на NSString, поэтому мы добавим эту деталь, и сначала мы опустим атрибут:

@interface NSString (AddingPercentEscapes)

- (NSString *) pcen;

@end

@implementation NSString (AddingPercentEscapes)

- (NSString *) pcen
{
   return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef) self, NULL, (CFStringRef) @"!*'();:@&=+$,/?%#[]", kCFStringEncodingUTF8);
}

@end

И тривиальное его использование:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
   NSString *test = @"This & than > other";

   NSLog(@"pcen: %@", [test pcen]);
}

При компиляции оператора pcen method return ARC смотрит на подпись, имя (pcen) не указывает передачу права собственности, и нет атрибута, поэтому ARC добавляет autorelease возвращаемой ссылки по выражению (__bridge_transfer NSString *) ... kCFStringEncodingUTF8), поскольку это выражение возвращает ссылку, принадлежащую pcen.

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

При компиляции вызова pcen в методе applicationDidFinishLaunching ARC снова смотрит на подпись, определяет, что текущий метод требует права собственности и что возвращенная ссылка не принадлежит и вставляет retain.

Вы можете проверить это, вызвав "Продукт > Сгенерировать вывод > Сборочный файл" в Xcode, в результирующей сборке вы увидите в коде для pcen что-то вдоль строк:

callq   _CFURLCreateStringByAddingPercentEscapes
movq    %rax, %rdi
callq   _objc_autoreleaseReturnValue
addq    $16, %rsp
popq    %rbp
ret

который показывает авторекламу, вставленную ARC, а в сборке для applicationDidFinishLaunching что-то вдоль строк:

callq   _objc_msgSend
movq    %rax, %rdi
callq   _objc_retainAutoreleasedReturnValue

который является вызовом pcen, за которым следует вставленный ARC.

Итак, ваш пример отлично работает без аннотации, ARC делает все правильно. Однако он также отлично работает с аннотацией, позволяет изменить интерфейс на:

@interface NSString (AddingPercentEscapes)

- (NSString *) pcen NS_RETURNS_RETAINED;

@end

Запустите (и проанализируйте) эту версию, и она также работает. Однако сгенерированный код изменился, ARC определяет, что он должен передавать право собственности на основе наличия атрибута, поэтому сборка для оператора return будет выглядеть следующим образом:

callq   _CFURLCreateStringByAddingPercentEscapes
addq    $16, %rsp
popq    %rbp
ret

ARC делает не вставить авторекламу. На сайте вызова сборка становится:

callq   _objc_msgSend
movq    -40(%rbp), %rdi         ## 8-byte Reload
movq    %rax, %rsi
movq    %rax, -48(%rbp)         ## 8-byte Spill
movb    $0, %al
callq   _NSLog

И здесь ARC делает не вставить сохранение.

Итак, обе версии являются "правильными", но что лучше?

Может показаться, что версия с атрибутом лучше, так как ARC не должна быть вставлена ​​в autorelease/retain; но среда выполнения оптимизирует эту последовательность (отсюда вызов _objc_retainAutoreleasedReturnValue, а не что-то вроде _objc_retain), поэтому стоимость не такая большая, как может показаться.

Однако правильный ответ ни...

Рекомендуемое решение состоит в том, чтобы полагаться на соглашения Cocoa/ARC и изменять имя вашего метода, например:

@interface NSString (AddingPercentEscapes)

- (NSString *) newPercentEscapedString;

@end

и связанные с ним изменения.

Сделайте это, и вы получите тот же код, что и pcen NS_RETURNS_RETAINED, поскольку ARC определяет, что он должен передать право собственности на имя new....

Этот ответ уже давно, надеюсь, выше сказанное поможет вам решить ответы на ваши два других примера!