10

以下面的例子:什麼時候需要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是正確的嗎?


而第三個例子:

​​

不知道該怎麼辦在這裏,作爲返回的對象可以是新建的或沒有。


還有最後一個問題;如果返回的對象是autorelease'ed方法的結果,則不需要NS_RETURNS_RETAINED。所以說,在最後一個例子返回了修訂,

return [NSArray arrayWithObject:@"Unknown"];

什麼是最好的做法呢?

回答

2

第一例

它是正確的把NS_RETURNS_RETAINED呢?

這是不正確 - 這裏沒有必要的屬性。添加屬性將違背命名約定,這是非常重要的。

更詳細地說,不需要任何屬性,因爲在使用(__bridge_transfer NSString*)的示例中,引用是轉移。有人可能會認爲CFCreated-Reference可能需要更多的東西,但只需將(__bridge_transfer NSString*)轉換爲ARC即可;爲它管理你。

如果您使用(__bridge NSString*)CF_*_Create_*_進行了類型轉換,那麼CFCreate函數返回的引用將不會傳輸到ARC,並且會引入泄漏。

作爲替代方案,如果您選擇(例如,使用CFRelease)明確地釋放返回的字符串可避免泄漏。

第二個例子

然而,圖形上下文是從方法的範圍內創建的,那麼在這裏也有NS_RETURNS_RETAINED是否正確?

使用屬性是不正確或不必要的。 ARC使用命名約定和屬性來確定要添加的引用計數操作 - 它理解您的程序。

與第一個示例不同,不應制定明確的__bridge_transfer

添加屬性會破壞命名約定。

第三個例子

- (NSArray *)places 
... 

不知道該怎麼辦在這裏,作爲返回的對象可以是新建的或沒有。

同樣,不應該使用任何屬性。不應該有明確的__bridge_transfer。 ARC瞭解ObjC約定,包括返回現有和新創建的對象。它會爲兩個路徑插入正確的引用計數操作。

還有最後一個問題;如果返回的對象是autorelease'ed方法的結果,則可能不需要NS_RETURNS_RETAINED。所以說,在最後一個例子返回了修訂,

return [NSArray arrayWithObject:@"Unknown"]; 

同樣,也需要沒有屬性。不應該進行明確的轉移。

在所有系統庫中只存在少數幾個屬性的用法。


我真的,真的,真的,真的建議不要使用這些屬性,尤其是:

  • 其中動態調度涉及(其中所有objc方法將會爲出線)
  • 其中的參數(消費)和結果(保留的回報)是ObjC類型

原因是參考轉移應該是實現的本地,並且很少真正需要偏離這一點;向後兼容可能是我能想到的「最好」理由。如果你控制了你的代碼,只需更新它來儘可能地做正確的事情,而不是引入這些屬性。這可以通過遵守命名慣例以及在適當的情況下將引用轉移到ARC來完成。

有關可以使用屬性並偏離命名約定的錯誤示例,請參閱:Deep copy of dictionaries gives Analyze error in Xcode 4.2

堅持使用命名約定的另一個好理由是,您並不總是知道如何使用您的程序。如果有人想在MRC翻譯使用你的程序,那麼他們將不得不寫一個念想這個不尋常的方案:

某處

- (NSString *)name NS_RETURNS_RETAINED; 

別處

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

非常感謝您清理所有這些。出於興趣,在什麼情況下會使用NS_RETURNS_RETAINED? – Max

+0

@Alec不用客氣。如果遵循慣例並遵守規則,我們許多人將永遠不需要使用此屬性。我已經提到向後兼容性(也就是說,如果你想保持一個不遵循Apple命名約定的設計)。蘋果的框架中也有一些有趣的用途; ''自我交換的unarchiving和'NSMakeCollectable'(一個垃圾收集另外還有一個消費屬性) - 這幾乎是所有iOS框架的一切。 (cont) – justin

+0

(cont)爲了在初始化和所有權'take'期間進行渠道分類,我在幾個(非常)內部函數(都使用靜態分派)中使用了消費屬性。總的來說,這些屬性非常少見,而且相當內在。 – justin

8

[這個答案是部分對賈斯汀給出的答覆提出長期評論/更正。先前的回答給出了我相信這個屬性以及ARC如何處理返回引用的語義的錯誤描述。]

答案在於ARC分析的工作原理和NS_RETURNS_RETAINED的含義。

ARC會分析您的來源以確定何時保留,釋放或自動釋放可保留的對象引用。

如果所有您的應用程序的來源是可利用的話,理論上的分析或許能夠確定從「第一原則」這一信息 - 從最小的表情和向外工作。

但是所有來源不可用 - 例如,一些已經在框架等編譯 - 所以當分析一個方法調用時,ARC不會查看方法的來源,而只是在它的簽名 - 它的名字,它的參數和返回值的類型。

僅考慮可保留對象類型ARC需要知道所有權是否轉移的返回值 - 在這種情況下,ARC需要發佈它在某個時刻 - 與否(例如自動釋放參考) - 在這種情況下,ARC將需要保留它,如果所有權是必需的。

ARC根據該方法的名稱和任何屬性確定此信息。從initnew開始的方法或者根據定義的所有權轉移方法,包括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方法return聲明ARC查看簽名,名稱(pcen)不表示所有權的轉移並且沒有屬性,因此ARC會添加由表達式(__bridge_transfer NSString *) ... kCFStringEncodingUTF8)返回的引用的autorelease,因爲該表達式將返回由pcen

重要:什麼表達的是並不重要,只pcen是否擁有它保留了參考 - 特別是__bridge_transfer沒有確定該方法返回引用的所有權。

applicationDidFinishLaunching方法中編譯pcen的方法時,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做正確的事情。然而,它也能正常工作與註釋,讓我們的界面更改爲:

@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確實插入保留。

所以兩個版本都是「正確的」,但哪個更好?

看起來這個屬性的版本更好,因爲沒有autorelease/retain需要被ARC插入;但運行時優化了這個順序(因此呼叫_objc_retainAutoreleasedReturnValue而不是像_objc_retain),所以成本並不像看起來那麼大。

然而正確答案是既不 ...

推薦的解決方案是依靠可可/ ARC公約和改變你的方法的名稱,例如:

@interface NSString (AddingPercentEscapes) 

- (NSString *) newPercentEscapedString; 

@end 

和相關的變化。

做到這一點,你會得到相同的代碼pcen NS_RETURNS_RETAINED作爲ARC確定是否應基於該new...轉移所有權。

這個答案已經(太)已經很久了,希望以上內容能夠幫助你解決其他兩個例子的問題!

+0

謝謝你CRD,非常非常豐富的答案。關於遵循'new ...'命名約定的建議,它看起來像'stringByAppendingString:'這樣的Cocoa方法不行。怎麼來的? – Max

+1

也是一種可能的更正:'從init或new開始,或者按照定義,所有權;所有其他方法都不會。「它不是'alloc','new'還是包含'copy'? – Max

+1

@Alec - 'new ...'與'string ...'(一般爲' ...')* class *方法。這些約定在ARC之前。前者是'alloc'&'init'類方法的約定。後者用於'alloc','init'和'autorelease'。 在你的例子中,你有一個* instance *方法創建一個新的對象。要讓ARC自動轉移所有權,該方法需要位於init,new或copy系列之一中。所以我選擇了'newPercentEscapedString',也許'copyWithPercentEscapes'會是一個更好的名字,請選擇! – CRD

相關問題