[この回答の一部は、ジャスティンの回答に対する長いコメント/修正です。その前の回答では、属性のセマンティクスと、ARC が参照を返す方法の両方について、誤った説明をしていると思います。]
その答えは、ARC 解析の仕組みと の意味にありNS_RETURNS_RETAINED
ます。
ARC はソースを分析して、保持可能なオブジェクト参照をいつ保持、解放、または自動解放するかを決定します。
アプリケーションのすべてのソースが利用可能である場合、理論的には、分析は「第一原理」からこの情報を決定できる可能性があります。つまり、最小の式から始めて外側に向かって作業します。
ただし、すべてのソースが利用できるわけではありません。たとえば、フレームワークなどですでにコンパイルされているものもあります。そのため、メソッド呼び出しを分析するとき、ARC はメソッドのソースを調べず、メソッドのシグネチャ (名前とパラメータの型、戻り値) のみを調べます。価値。
保持可能なオブジェクト型の戻り値だけを考えると、ARC は所有権が譲渡されているかどうかを知る必要があります。その場合、ARC はある時点で所有権を解放する必要があります。所有権が必要な場合。
ARCは、メソッドの名前と属性に基づいてこの情報を決定します。定義上、所有権の譲渡で始まる、init
または譲渡new
を含むメソッド。copy
他のすべての方法はそうではありません。この属性NS_RETURNS_RETAINED
は、メソッドがその名前に関係なく、返された参照の所有権を譲渡することを ARC に通知します。
それが話の半分です...残りの半分は、ARCがreturn
メソッド本体のステートメントをどのように処理するかです。
Areturn
は実際には割り当ての一種であり、保持可能なオブジェクト参照の割り当てを行う場合、ARC は、現在の所有権と参照に関する知識と宛先の要件に基づいて、参照を保持するか、自動解放するか、そのままにしておく必要があるかを判断します。
ステートメントの場合return
、宛先の要件は、当然のことながら、メソッドの名前と署名で指定された属性によって決まります。署名が所有権が譲渡されていることを示している場合、ARC は保持された参照を返します。それ以外の場合は、自動解放された参照を返します。
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 はautorelease
式によって返された参照の を追加(__bridge_transfer NSString *) ... kCFStringEncodingUTF8)
しますpcen
。
重要:式が 何でpcen
あるかは重要ではなく、それが保持する参照を所有しているかどうかだけです。特に、__bridge_transfer
はメソッドによって返される参照の所有権を決定しません。
pcen
メソッド ARC内のへの呼び出しをapplicationDidFinishLaunching
再度コンパイルすると、署名が調べられ、現在のメソッドには所有権が必要であり、返された参照が所有されていないと判断され、retain
.
これを確認するには、Xcode で [Product] > [Generate Output] > [Assembly File] を呼び出します。結果のアセンブリには、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...
この回答はすでに (長すぎます) です。うまくいけば、上記が他の 2 つの例に対する回答を解決するのに役立つことを願っています!