[這個答案是部分對賈斯汀給出的答覆提出長期評論/更正。先前的回答給出了我相信這個屬性以及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
方法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...
轉移所有權。
這個答案已經(太)已經很久了,希望以上內容能夠幫助你解決其他兩個例子的問題!
非常感謝您清理所有這些。出於興趣,在什麼情況下會使用NS_RETURNS_RETAINED? – Max
@Alec不用客氣。如果遵循慣例並遵守規則,我們許多人將永遠不需要使用此屬性。我已經提到向後兼容性(也就是說,如果你想保持一個不遵循Apple命名約定的設計)。蘋果的框架中也有一些有趣的用途; ''自我交換的unarchiving和'NSMakeCollectable'(一個垃圾收集另外還有一個消費屬性) - 這幾乎是所有iOS框架的一切。 (cont) – justin
(cont)爲了在初始化和所有權'take'期間進行渠道分類,我在幾個(非常)內部函數(都使用靜態分派)中使用了消費屬性。總的來說,這些屬性非常少見,而且相當內在。 – justin