13

以下の例を見てください。

- (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ここにもあるのは正しいですか?


そして 3 番目の例:

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

返されたオブジェクトが新しく作成されたかどうかにかかわらず、ここで何をすべきかわかりません。


そして最後の質問です。NS_RETURNS_RETAINED返されたオブジェクトが自動解放されたメソッドの結果である場合、おそらく必要ありません。したがって、最後の例の戻り値が次のように修正されたとします。

return [NSArray arrayWithObject:@"Unknown"];

その場合、ベストプラクティスは何ですか?

4

2 に答える 2

19

[この回答の一部は、ジャスティンの回答に対する長いコメント/修正です。その前の回答では、属性のセマンティクスと、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メソッドステートメントをコンパイルするとき、returnARC はシグネチャを確認します。名前 ( 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 つの例に対する回答を解決するのに役立つことを願っています!

于 2012-08-27T21:19:16.837 に答える
4

最初の例

NS_RETURNS_RETAINEDをそこに置くのは正しいですか?

これは正しくありません。ここでは属性は必要ありません。属性を追加すると、従うことが非常に重要な命名規則に反します。

より詳細には、参照はを使用した例で転送さ(__bridge_transfer NSString*)れるため、属性は必要ありません。CFCreated-Referenceにはさらに何かが必要かもしれないと思うかもしれませんが、(__bridge_transfer NSString*)その参照をARCに転送するために必要なのはそれだけです。それがあなたのために管理するために。

を使用してタイプキャストした場合(__bridge NSString*)CF_*_Create_*_、CFCreate関数によって返される参照はARCに転送されず、リークが発生します。

別の方法として、返された文字列を明示的に解放することを選択した場合(たとえば、を使用して)、そのリークを回避できますCFRelease

2番目の例

ただし、取得元のグラフィックスコンテキストはメソッドのスコープ内で作成されたので、ここにNS_RETURNS_RETAINEDも含めるのは正しいですか?

属性を使用することは正しくないか、必要ではありません。ARCは、命名規則と属性を使用して、追加する参照カウント操作を決定します-プログラムを理解します。

最初の例とは異なり、明示的に__bridge_transferするべきではありません。

属性を追加すると、命名規則が破られます。

3番目の例

- (NSArray *)places 
...

返されたオブジェクトが新しく作成されるかどうかにかかわらず、ここで何をすべきかわかりません。

繰り返しますが、属性は使用しないでください。明示的に__bridge_transferするべきではありません。ARCは、既存のオブジェクトと新しく作成されたオブジェクトを返すなど、ObjCの規則を理解しています。両方のパスに適切な参照カウント操作を挿入します。

そして最後の質問です。返されるオブジェクトが自動解放されたメソッドの結果である場合、おそらくNS_RETURNS_RETAINEDは必要ありません。つまり、最後の例のリターンが次のように修正されたとしましょう

return [NSArray arrayWithObject:@"Unknown"];

繰り返しますが、属性は必要ありません。明示的な転送は行わないでください。

すべてのシステムライブラリに存在する属性の使用法はほんの一握りです。


私は本当に、本当に、本当に、本当に、これらの属性を使用しないようにアドバイスします。

  • 動的ディスパッチが関係する場合(すべてのobjcメソッドが適格となる)
  • ここで、パラメーター(消費)と結果(保持されたリターン)はObjCタイプです

理論的根拠は、参照転送は実装に対してローカルである必要があり、それから逸脱する必要はめったにないということです。下位互換性は、おそらく私が考えることができる「最良の」理由です。コードを制御できる場合は、これらの属性を導入するのではなく、可能な限り正しいことを行うようにコードを更新してください。これは、命名規則を順守し、必要に応じて参照をARCに転送することで実現できます。

属性を使用したり、命名規則から逸脱したりする可能性のあるエラーの例については、「辞書を深くコピーすると、Xcode4.2でAnalyzeエラーが発生する」を参照してください。

命名規則に固執するもう1つの理由は、プログラムがどのように使用されるかを常に知っているとは限らないことです。誰かがあなたのプログラムをMRC翻訳で使用したい場合、彼らはこのように読む珍しいプログラムを書かなければならないでしょう:

どこか

- (NSString *)name NS_RETURNS_RETAINED;

他の場所

NSString * name = obj.name;
NSLog(@"%@", name);
[name release]; // << ME: not a mistake. triple checked.
于 2012-08-27T06:17:00.857 に答える