13

Take the below example:

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

Is it correct to put the NS_RETURNS_RETAINED there?


Another example:

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

That seems more complicated, as the returned UIImage is the result of a 'Get' method. However, the graphics context it's getting it from was created within the scope of the method, so is it correct to also have NS_RETURNS_RETAINED here?


And a third example:

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

No idea what to do here, as the returned object could be newly created or not.


And one last question; presumably NS_RETURNS_RETAINED isn't required if the object returned is the result of an autorelease'ed method. So say the return in the last example was amended to

return [NSArray arrayWithObject:@"Unknown"];

what would be best practise then?

justin
  • 104,054
  • 14
  • 179
  • 226
Max
  • 2,760
  • 1
  • 28
  • 47

2 Answers2

19

[This answer is partly a long comment/correction to the answer given by Justin. That previous answer gives I believe an incorrect description of the semantics of both the attribute and how ARC handles returning references.]

The answer lies in how ARC analysis works and the meaning of NS_RETURNS_RETAINED.

ARC analyzes your source to determine when to retain, release, or autorelease retainable object references.

If all the source for your application was available then, in theory, an analysis might be able to determine this information from "first principles" - starting with the smallest expressions and working outwards.

However all the source is not available - e.g. some is already compiled in frameworks etc. - so when analyzing a method call ARC does not look at the source of the method but only at its signature - its name and the types of its parameters and return value.

Considering just a return value of retainable object type ARC needs to know whether the ownership is being transferred - in which case ARC will need to release it at some point - or not (e.g. an autoreleased reference) - in which case ARC will need to retain it if ownership is required.

ARC determines this information based on the name of the method and any attributes. Methods starting with init or new or containing copy transfer, by definition, ownership; all other methods do not. The attribute NS_RETURNS_RETAINED informs ARC that a method, regardless of its name, transfers ownership of its returned reference.

That is half the story... the other half is how ARC handles the return statement in a method body.

A return is really a type of assignment, and when doing a retainable object reference assignment ARC determines whether the reference needs to be retained, autoreleased, or left as is based on its knowledge of the current ownership and reference and the requirements of the destination.

For a return statement the requirements of the destination are, unsurprisingly, determined by the name of the method and any attributes specified on the signature. If the signature indicates that ownership is being transferred then ARC will return a retained reference, otherwise it will return an autoreleased one.

It is important to understand that ARC is working on both sides of a method call, it ensures the appropriate reference is returned and determines how that returned reference is handled.

With all that preamble we can look at your first example. It looks like you are writing a method on NSString, so we'll add that detail, and first we'll omit the attribute:

@interface NSString (AddingPercentEscapes)

- (NSString *) pcen;

@end

@implementation NSString (AddingPercentEscapes)

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

@end

And a trivial use of it:

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

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

When compiling the pcen method return statement ARC looks at the signature, the name (pcen) does not indicate transfer of ownership and there is no attribute, so ARC adds an autorelease of the reference returned by the expression (__bridge_transfer NSString *) ... kCFStringEncodingUTF8) as that expression returns a reference owned by pcen.

Important: what the expression is is not important, only whether pcen owns the reference it retains - in particular the __bridge_transfer does not determine the ownership of the reference returned by the method.

When compiling the call to pcen in the applicationDidFinishLaunching method ARC again looks at the signature, determines the current method requires ownership and that the returned reference is not owned and inserts a retain.

You can verify this by invoking "Product > Generate Output > Assembly File" in Xcode, in the resulting assembly you will see in the code for pcen something along the lines of:

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

which shows the autorelease inserted by ARC, and in the assembly for applicationDidFinishLaunching something along the lines of:

callq   _objc_msgSend
movq    %rax, %rdi
callq   _objc_retainAutoreleasedReturnValue

which is the call to pcen followed by the ARC inserted retain.

So your example works fine without the annotation, ARC does the right thing. However it also works fine with the annotation, let's change the interface to:

@interface NSString (AddingPercentEscapes)

- (NSString *) pcen NS_RETURNS_RETAINED;

@end

Run (and Analyze) this version and it also works. However the generated code has changed, ARC determines it should transfer ownership based on the presence of the attribute, so the assembly for the return statement becomes:

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

ARC does not insert an autorelease. At the call site the assembly becomes:

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

And here ARC does not insert a retain.

So both versions are "correct", but which is better?

It might seem the version with the attribute is better as no autorelease/retain needs to be inserted by ARC; but the runtime optimizes this sequence (hence the call to _objc_retainAutoreleasedReturnValue rather than something like _objc_retain) so the cost is not as large as it may appear.

However the correct answer is neither...

The recommended solution is to rely on Cocoa/ARC conventions and change the name of your method, e.g.:

@interface NSString (AddingPercentEscapes)

- (NSString *) newPercentEscapedString;

@end

and the associated changes.

Do this and you'll get the same code as pcen NS_RETURNS_RETAINED as ARC determines it should transfer ownership based on the name new....

This answer is (too) long already, hopefully the above will help you work out the answers to your other two examples!

CRD
  • 52,522
  • 5
  • 70
  • 86
  • Thank you CRD, very very informative answer. Regarding your recommendation of following the `new...` naming convention, it appears Cocoa methods like `stringByAppendingString:` don't. How come? – Max Aug 27 '12 at 21:40
  • 1
    Also a possible correction: `Methods starting with init or new or containing copy transfer, by definition, ownership; all other methods do not.` Is it not `alloc`, `new` and containing `copy`? – Max Aug 27 '12 at 21:47
  • 1
    @Alec - `new...` vs. `string...` (in general `...`) *class* methods. These conventions pre-date ARC. The former is the convention for class methods which `alloc` & `init`; the latter for those which `alloc`, `init` and `autorelease`. In your example you have an *instance* method which creates a new object. To have ARC automatically transfer ownership the method needs to be in one of the init, new or copy families. So I picked `newPercentEscapedString`, maybe `copyWithPercentEscapes` would have been a better name, take your pick! – CRD Aug 27 '12 at 22:44
  • @Alec - re `alloc`. Correct, `alloc` does return a reference the callee owns. However it is not normally mentioned in the list. An `init` method *consumes* (i.e. takes ownership of) its argument (which came from `alloc`) and returns a reference the callee owns - so it is in the list. *[Note: there is no guarantee that `init` returns the same reference it was passed, hence *consumes* - if it returns a different reference the passed in one is released. Classes such as `NSNumber` might do this, e.g. returning the same reference from distinct calls which pass the same value.]* – CRD Aug 27 '12 at 22:50
  • @Justin - you start by saying in the 1st example it is *incorrect* to add the attribute - it isn't. You then explained this by referring to the `__bridge_transfer` and saying this means you no attribute, again wrong. What is in the `return` expression is effectively irrelevant to ARC, only the ownership of the reference returned. In this example the `__bridge_transfer` causes the resulting reference to be owned by *pcen*. Therefore logically the attribute should be added so this ownership is transferred to the callee, in practice the method is best renamed to follow convention so this happens. – CRD Aug 28 '12 at 00:23
  • I get what you mean for class methods like `stringWithFormat:`. However, `stringByAppendingString:` is an instance method. And surely in terms of ownership it's identical to my `pcen` method? – Max Aug 28 '12 at 00:51
  • @CRD well, that's an interesting way to look at it… i'll put it this way: if you 1) wrote a library with such irreverence for naming conventions that you would opt to use these attributes in place of properly abstracted local reference transfers and 2) provided that library for others to use then 3) asked those people what they thought of your library -- you would be told that you did it wrong. programs aren't only for compilers with ARC flags enabled -- ARC sources can be used alongside MRC sources in the same program, but more importantly -- programs are also for humans. good luck – justin Aug 28 '12 at 01:19
  • @Alec - `stringByAppendingString:` does not return a reference owned by the caller, if the result needs to be kept around then it must be retained (either explicitly in MRC, or automatically with ARC). In your example I assume you wish the caller to have ownership as you added the attribute, hence the suggested name `copyWithPercentEscapes`. If you don't wish that then call the method `stringWithPercentEscapes` and ARC will insert the required autorelease before the method returns. What you did by adding the attribute was not "wrong", but the recommendation is to follow the naming convention. – CRD Aug 28 '12 at 01:33
  • @Justin - it is not your conclusion which was wrong; the general recommendation is to stick with the naming conventions and not to use the attribute. It is your explanation of the meaning and function of the attribute, involving __bridge_transfer etc., that erred and presents a completely wrong semantics for how ARC returns a reference. The naming convention just implicitly adds the attribute. So I'm afraid your answer as a whole is I believe wrong due to this, even though it ended with the right recommendation. – CRD Aug 29 '12 at 20:20
  • @CRD i don't see it as wrong. terse, but not wrong. i will add detail (at some point) to avoid these ambiguities and misinterpretations. – justin Aug 29 '12 at 20:59
4

First Example

Is it correct to put the NS_RETURNS_RETAINED there?

It is incorrect -- no attribute is necessary here. Adding the attribute would go against naming conventions, which are very important to follow.

In more detail, no attribute is required because the reference is transferred in the example using (__bridge_transfer NSString*). One might suppose that a CFCreated-Reference may need something more, but (__bridge_transfer NSString*) is all that is needed to transfer that reference off to ARC; for it to manage for you.

If you were to have typecasted using (__bridge NSString*)CF_*_Create_*_, then the reference returned by the CFCreate function would not be transferred to ARC, and a leak would be introduced.

(As an alternative, that leak could be avoided if you opted to release the returned string explicitly (e.g. using CFRelease).)

Second Example

However, the graphics context it's getting it from was created within the scope of the method, so is it correct to also have NS_RETURNS_RETAINED here?

It is not correct or necessary to use an attribute. ARC uses naming conventions and attributes to determine the reference count operations to add -- It understands your program.

Unlike the first example, an explicit __bridge_transfer should not be made.

Adding the attribute would break naming conventions.

Third Example

- (NSArray *)places 
...

No idea what to do here, as the returned object could be newly created or not.

Again, no attribute should be used. An explicit __bridge_transfer should not be made. ARC understands ObjC conventions, including returning existing and newly created objects. It will insert the right reference count operations for both paths.

And one last question; presumably NS_RETURNS_RETAINED isn't required if the object returned is the result of an autorelease'ed method. So say the return in the last example was amended to

return [NSArray arrayWithObject:@"Unknown"];

Again, no attribute is needed. An explicit transfer should not be made.

Only a handful of uses of the attribute exist across all system libraries.


I really, really, really, really advise against using these attributes, particularly:

  • where dynamic dispatch is involved (which all objc methods would qualify as)
  • where the parameters (consume) and results (returns retained) are ObjC types

The rationale is that reference transfers should be local to the implementations, and there is rarely a real need to deviate from that; backwards compatibility is probably the "best" reason I can think of. If you have control of your code, just update it to do the right thing wherever possible rather than introducing these attributes. This can be accomplished by adhering to naming conventions, and by transferring references to ARC where appropriate.

For some examples of the errors you can run into using attributes and deviating from naming conventions, see: Deep copy of dictionaries gives Analyze error in Xcode 4.2 .

Another good reason to stick with naming conventions is that you don't always know how your program will be used. If somebody wants to use your program in MRC translations, then they will have to write unusual programs which read like this:

someplace

- (NSString *)name NS_RETURNS_RETAINED;

elsewhere

NSString * name = obj.name;
NSLog(@"%@", name);
[name release]; // << ME: not a mistake. triple checked.
Community
  • 1
  • 1
justin
  • 104,054
  • 14
  • 179
  • 226
  • Thanks very much for clearing all that up. Out of interest, in what situation would NS_RETURNS_RETAINED be used? – Max Aug 27 '12 at 06:52
  • @Alec you're welcome. if you follow conventions and play by the rules, many of us will never need to use this attribute. i mentioned backwards compatibility already (that is, if you want to maintain a design which does not follow Apple's naming conventions). there are also a few interesting uses in Apple's frameworks; `self`-swapping upon unarchiving and `NSMakeCollectable` (a garbage collection addition which also has a consume attribute) -- and that's almost everything in all the iOS frameworks. (cont) – justin Aug 27 '12 at 08:04
  • (cont) i use the consumed attribute in a few (very) internal functions (which all use static dispatch) for the purposes of funneling during initialization and ownership 'takes'. overall, these attributes are very rare to use and quite internal. – justin Aug 27 '12 at 08:05
  • @Alec - while the final recommendation is valid the explanation given in this answer is I believe *wrong*, sorry Justin. Not enough space to explain why here, I will add that in as a separate answer. – CRD Aug 27 '12 at 19:56
  • @CRD go for it - unable to go to sleep until after i've learnt something :) – justin Aug 27 '12 at 19:58
  • The explanation in this answer is unfortunately wrong. When it says "no attribute is required because the reference is transferred in the example using `(__bridge_transfer NSString*)`" this is invalid; the fact that a reference is transferred to ARC in the expression and the semantics and need, or otherwise, of the attribute are completely orthogonal issues. I've added a separate answer with the details. – CRD Aug 30 '12 at 10:06