When is NS_RETURNS_RETAINED needed?

前端 未结 2 1645
慢半拍i
慢半拍i 2021-02-07 15:24

Take the below example:

- (NSString *)pcen NS_RETURNS_RETAINED {
    return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge         


        
2条回答
  •  有刺的猬
    2021-02-07 16:08

    [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!

提交回复
热议问题