セレクターが不明であるため、performSelectorがリークを引き起こす可能性があります


1258

ARCコンパイラから次の警告が表示されます。

"performSelector may cause a leak because its selector is unknown".

これが私がやっていることです:

[_controller performSelector:NSSelectorFromString(@"someMethod")];

なぜこの警告が出るのですか?セレクターが存在するかどうかをコンパイラーがチェックできないことを理解していますが、なぜリークが発生するのですか?そして、この警告を受け取らないようにコードを変更するにはどうすればよいですか?


3
変数の名前は動的で、他の多くのものに依存します。私が存在しないものを呼ぶリスクがありますが、それは問題ではありません。
Eduardo Scoz、2011

6
@mattなぜオブジェクトでメソッドを動的に呼び出すのは悪い習慣でしょうか?NSSelectorFromString()の目的はすべて、このプラクティスをサポートすることではありませんか?
Eduardo Scoz、2011

7
あなたべき/可能性もテスト[_controller respondsToSelector:mySelector] performSelectorを経由して、それを設定する前に、:
mattacular

50
@mattacular Wish私が反対票を投じることができれば:「それは...悪い習慣です。」
ctpenrose 2012

6
文字列がリテラルであることがわかっている場合は、@ selector()を使用して、コンパイラーがセレクター名を識別できるようにします。実際のコードが、実行時に作成または提供される文字列でNSSelectorFromString()を呼び出す場合は、NSSelectorFromString()を使用する必要があります。
Chris Page

回答:


1211

解決

コンパイラは、これについて何らかの理由で警告しています。この警告が単に無視されることは非常にまれであり、簡単に回避できます。方法は次のとおりです。

if (!_controller) { return; }
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [_controller methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(_controller, selector);

またはもっと簡潔に(読みづらいとガードなしでは):

SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);

説明

ここでは、コントローラーに対応するメソッドのC関数ポインターをコントローラーに要求しています。すべてNSObjectのはに応答しmethodForSelector:ますがclass_getMethodImplementation、Objective-Cランタイムでも使用できます(などのプロトコル参照しかない場合に役立ちますid<SomeProto>)。これらの関数ポインターはIMPs と呼ばれ、単純なtypedefed関数ポインター(id (*IMP)(id, SEL, ...)1です。これは、メソッドの実際のメソッドシグネチャに近い場合がありますが、常に正確に一致するとは限りません。

を取得したらIMP、ARCが必要とするすべての詳細(2つの暗黙的な隠し引数self_cmdすべてのObjective-Cメソッド呼び出しの詳細を含む)を含む関数ポインターにキャストする必要があります。これは3行目で処理されます((void *)右側は、ポインターの型が一致しないため、何をしているのかを知っていて、警告を生成しないことをコンパイラーに通知するだけです)。

最後に、関数ポインター2を呼び出します。

複雑な例

セレクターが引数を取るか値を返す場合、少し変更する必要があります。

SEL selector = NSSelectorFromString(@"processRegion:ofView:");
IMP imp = [_controller methodForSelector:selector];
CGRect (*func)(id, SEL, CGRect, UIView *) = (void *)imp;
CGRect result = _controller ?
  func(_controller, selector, someRect, someView) : CGRectZero;

警告の理由

この警告の理由は、ARCではランタイムが、呼び出しているメソッドの結果をどう処理するかを知る必要があるためです。結果は何もすることができますvoidintcharNSString *id、などARCは通常、あなたが作業しているオブジェクトタイプのヘッダからこの情報を取得します。

ARCが戻り値について考慮すべきことは、実際には4つだけです:4

  1. 非オブジェクト型(無視するvoidintなど)
  2. オブジェクト値を保持し、使用されなくなったら解放します(標準の想定)
  3. 使用されなくなったときに新しいオブジェクト値を解放します(init/ copyファミリのメソッド、またはで属性付けされたメソッドns_returns_retained
  4. 何もせず、返されたオブジェクト値がローカルスコープで有効であると想定します(最も内側のリリースプールが排出され、で属性が付与されるまでns_returns_autoreleased

の呼び出しmethodForSelector:は、呼び出しているメソッドの戻り値がオブジェクトであることを前提としていますが、保持/解放しません。したがって、上記の#3のようにオブジェクトが解放されることになっていると、リークが発生する可能性があります(つまり、呼び出しているメソッドが新しいオブジェクトを返します)。

その戻り値voidまたは他の非オブジェクトを呼び出そうとしているセレクターの場合、コンパイラー機能を有効にして警告を無視することができますが、危険な場合があります。Clangがローカル変数に割り当てられていない戻り値を処理する方法を数回繰り返すのを見てきました。ARCを有効にしてmethodForSelector:、使用したくない場合でも、返されたオブジェクト値を保持および解放できない理由はありません。コンパイラの観点から見ると、それは結局オブジェクトです。つまりsomeMethod、呼び出しているメソッドがを含む非オブジェクト(を含むvoid)を返している場合、ガベージポインターの値が保持/解放されてクラッシュする可能性があります。

追加の引数

1つの考慮事項は、これは同じ警告が発生することでperformSelector:withObject:あり、そのメソッドがパラメーターをどのように消費するかを宣言しないと、同様の問題が発生する可能性があるということです。ARCでは、消費されたパラメーターを宣言できます。メソッドがパラメーターを消費した場合、おそらく最終的にはゾンビにメッセージを送信してクラッシュします。ブリッジドキャスティングでこれを回避する方法はいくつかありますが、実際には、IMP上記のと関数ポインタの方法論を単に使用する方が良いでしょう。消費されたパラメーターがめったに問題にならないので、これは起こりそうにありません。

静的セレクター

興味深いことに、コンパイラーは静的に宣言されたセレクターについて文句を言うことはありません。

[_controller performSelector:@selector(someMethod)];

これは、コンパイラーがコンパイル時にセレクターとオブジェクトに関するすべての情報を実際に記録できるためです。何かについて何も仮定する必要はありません。(私は1年ほど前にソースを調べて確認しましたが、現時点ではリファレンスがありません。)

抑制

この警告の抑制が必要であり、優れたコード設計が必要となる状況を考えるために、私は何も考えていません。この警告を止めることが必要だった経験がある場合は、誰かが共有してください(上記では適切に処理されません)。

もっと

これNSMethodInvocationを処理するためにを構築することも可能ですが、これを行うにはより多くのタイピングが必要であり、速度も遅いため、それを行う理由はほとんどありません。

歴史

performSelector:メソッドのファミリーが最初にObjective-Cに追加されたとき、ARCは存在しませんでした。ARCの作成中、Appleは、名前付きセレクターを介して任意のメッセージを送信するときにメモリをどのように処理するかを明示的に定義する他の手段を開発者に案内する方法として、これらのメソッドに対して警告を生成することを決定しました。Objective-Cでは、開発者は生の関数ポインターにCスタイルのキャストを使用してこれを行うことができます。

Swiftの導入に伴い、Apple performSelector:一連のメソッドを「本質的に安全ではない」と文書化し、Swiftでは利用できなくなりました。

時とともに、私たちはこの進歩を見てきました:

  1. Objective-Cの初期バージョンではperformSelector:(手動メモリ管理)が可能です
  2. ARCを備えたObjective-Cは、 performSelector:
  3. SwiftはperformSelector:これらのメソッドへのアクセス権を持たず、「本質的に安全ではない」と文書化しています

ただし、名前付きセレクタに基づいてメッセージを送信するという考えは、「本質的に安全ではない」機能ではありません。このアイデアは、Objective-Cや他の多くのプログラミング言語で長い間成功裏に使用されてきました。


1すべてのObjective-Cメソッドには2つの隠し引数がselfあり_cmd、それらはメソッドを呼び出すときに暗黙的に追加されます。

2NULL Cでは関数の呼び出しは安全ではありません。コントローラーの存在を確認するために使用されるガードは、オブジェクトがあることを確認します。したがって、IMPfromを取得することがわかりますmethodForSelector:_objc_msgForwardメッセージ転送システムへのエントリである場合もあります)。基本的に、ガードを配置すると、呼び出す関数があることがわかります。

3実際には、オブジェクトを次のように宣言idし、すべてのヘッダーをインポートしていない場合、誤った情報を取得する可能性があります。コンパイラが問題ないと考えるコードでクラッシュが発生する可能性があります。これは非常にまれですが、発生する可能性があります。通常、2つのメソッドシグネチャのどちらから選択するかがわからないという警告が表示されます。

4上でARC基準参照保持戻り値保持されない戻り値の詳細については。


@wbyoungコードが保持の問題を解決する場合、performSelector:メソッドがこの方法で実装されていないのはなぜでしょうか。厳密なメソッドシグネチャ(を返すid、1つまたは2つidのs をとる)があるため、プリミティブ型を処理する必要はありません。
Tricertops 2013年

1
@Andy引数は、メソッドのプロトタイプの定義に基づいて処理されます(保持/解放されません)。懸念は主に戻り値の型に基づいています。
wbyoung 2014年

2
「複雑な例」では、Cannot initialize a variable of type 'CGRect (*)(__strong id, SEL, CGRect, UIView *__strong)' with an rvalue of type 'void *'最新のXcodeを使用するとエラーが発生します。(5.1.1)それでも、たくさん学びました!
スタンジェームズ

2
void (*func)(id, SEL) = (void *)imp;コンパイルされません、私はそれを置き換えましたvoid (*func)(id, SEL) = (void (*)(id, SEL))imp;
Davyd Geyl 2014年

1
変更void (*func)(id, SEL) = (void *)imp;する<…> = (void (*))imp;<…> = (void (*) (id, SEL))imp;
アイザックOsipovich Dunayevsky

1182

Xcode 4.2のLLVM 3.0コンパイラでは、次のように警告を抑制できます。

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self.ticketTarget performSelector: self.ticketAction withObject: self];
#pragma clang diagnostic pop

複数の場所でエラーが発生し、Cマクロシステムを使用してプラグマを非表示にする場合は、マクロを定義して警告を抑制しやすくすることができます。

#define SuppressPerformSelectorLeakWarning(Stuff) \
    do { \
        _Pragma("clang diagnostic push") \
        _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
        Stuff; \
        _Pragma("clang diagnostic pop") \
    } while (0)

次のようなマクロを使用できます。

SuppressPerformSelectorLeakWarning(
    [_target performSelector:_action withObject:self]
);

実行されたメッセージの結果が必要な場合、これを行うことができます:

id result;
SuppressPerformSelectorLeakWarning(
    result = [_target performSelector:_action withObject:self]
);

このメソッドは、最適化がNone以外に設定されている場合にメモリリークを引き起こす可能性があります。
Eric

4
@Ericいいえ、「initSomething」、「newSomething」、「somethingCopy」などの面白いメソッドを呼び出さない限り、できません。
Andrey Tarantsov 2012

3
@Julianこれは機能しますが、ファイル全体の警告をオフにします。それが必要ない場合や必要ない場合があります。それをWrappping poppush-pragmasは非常にクリーンで、より安全です。
Emil

2
これは、コンパイラを沈黙させるだけです。これは問題を解決しません。セレクターが存在しない場合は、かなり混乱しています。
アンドラトドレスク2014年

2
これは、if ([_target respondsToSelector:_selector]) {または類似のロジックでラップされている場合にのみ使用してください。

208

これについての私の推測はこれです:セレクターはコンパイラーにとって不明であるため、ARCは適切なメモリー管理を強制できません。

実際、メモリ管理が特定の規則によってメソッドの名前に関連付けられている場合があります。特に、便利なコンストラクターmakeメソッドを考えています。前者は慣例により自動解放されたオブジェクトを返します。後者は保持オブジェクトです。規則はセレクターの名前に基づいているため、コンパイラーがセレクターを認識していない場合、適切なメモリー管理規則を適用できません。

これが正しい場合、メモリ管理に関してすべてが問題ないことを確認すれば(たとえば、メソッドが割り当てたオブジェクトをメソッドが返さないこと)、コードを安全に使用できると思います。


5
答えてくれてありがとう。何が起こっているのかを調べるために、これについて詳しく調べます。警告をバイパスして非表示にする方法についてのアイデアはありますか?安全な呼び出しとは何かを警告がコードに永遠に残るのは嫌です。
Eduardo Scoz、2011

84
だから私は、フォーラムのアップルの誰かから、これが実際に事実であるという確認を得た。将来のリリースでこの警告を無効にできるように、忘れられたオーバーライドを追加する予定です。ありがとう。
Eduardo Scoz、2011

5
この答えは、いくつかの質問を提起します。たとえば、ARCが規則とメソッド名に基づいて何かをリリースするタイミングを決定しようとする場合、それはどのように「参照カウント」ですか?ARCがコードが特定の規則に従うことを想定している場合、どの規則に従っても参照を実際に追跡するのではなく、記述した動作は完全に恣意的なものよりわずかに優れているように聞こえます。
aroth

8
ARCは、コンパイル時に保持とリリースを追加するプロセスを自動化します。これはガベージコレクションではありません(そのため、非常に高速でオーバーヘッドが低くなっています)。それはまったく恣意的ではありません。デフォルトのルールは、何十年も一貫して適用されてきた確立されたObjC規則に基づいています。これにより__attribute、メモリ管理を説明するすべてのメソッドに明示的にを追加する必要がなくなります。しかし、それはまた、コンパイラーがこのパターンを適切に処理することを不可能にします(以前は非常に一般的でしたが、近年より堅牢なパターンに置き換えられました)。
Rob Napier

8
したがって、タイプのivarを使用できなくなりSEL、状況に応じて異なるセレクターを割り当てることができなくなりますか?行く方法、動的言語...
ニコラス・ミアリ

121

プロジェクトのビルド設定で、その他の警告フラグWARNING_CFLAGS)の下に、
-Wno-arc-performSelector-leaks

ここで、呼び出すセレクターによってオブジェクトが保持またはコピーされないことを確認してください。


12
プロジェクト全体ではなく、特定のファイルに同じフラグを追加できることに注意してください。[ビルドフェーズ]-> [ソースのコンパイル]を確認すると、ファイルごとにコンパイラフラグを設定できます(ARCからファイルを除外する場合と同じように)。私のプロジェクトでは、このように1つのファイルでセレクターを使用する必要があるため、ファイルを除外して他のファイルを残しました。
Michael

111

コンパイラが警告を上書きできるようになるまでの回避策として、ランタイムを使用できます

objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));

の代わりに

[_controller performSelector:NSSelectorFromString(@"someMethod")];

あなたはする必要があります

#import <objc/message.h>


8
ARCはCocoaの規則を認識し、それらの規則に基づいて保持とリリースを追加します。Cはこれらの規則に従っていないため、ARCでは手動のメモリ管理手法を使用する必要があります。CFオブジェクトを作成する場合は、それをCFRelease()する必要があります。dispatch_queue_create()を実行する場合は、dispatch_release()を実行する必要があります。結論として、ARC警告を回避したい場合は、Cオブジェクトと手動のメモリ管理を使用して警告を回避できます。また、ファイルごとに-fno-objc-arcコンパイラフラグを使用して、ファイルごとにARCを無効にすることができます。
jluckyiv 2011

8
キャストなしではできません。Varargsは、明示的に型指定された引数リストとは異なります。通常は偶然に機能しますが、「偶然」が正しいとは考えていません。
bbum 2011

21
それをしないでください、[_controller performSelector:NSSelectorFromString(@"someMethod")];そしてobjc_msgSend(_controller, NSSelectorFromString(@"someMethod"));同等ではありません!見ていメソッドシグネチャの不一致のObjective-Cの弱い型付けに大きな弱点彼らは徹底的に問題を説明しているが。
2011年

5
@ 0xcedこの場合、問題ありません。objc_msgSendは、performSelector:またはそのバリアントで正しく機能するセレクターに対してメソッドシグネチャの不一致を作成しません。これは、パラメーターとしてオブジェクトを取得するだけだからです。すべてのパラメーターがポインター(オブジェクトを含む)、double、NSInteger / longであり、戻り値の型がvoid、ポインター、またはlongである限り、objc_msgSendは正しく機能します。
Matt Gallagher

88

実行セレクターがあるファイルでのみエラーを無視するには、次のように#pragmaを追加します。

#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

これにより、この行の警告は無視されますが、プロジェクトの他の部分では引き続き許可されます。


6
問題のメソッドの直後に警告をオンに戻すこともできます#pragma clang diagnostic warning "-Warc-performSelector-leaks"。警告をオフにした場合は、できるだけ早くオンに戻したいので、予期しない別の警告を誤って通過させません。これが問題である可能性は低いですが、警告をオフにするときはいつも私の慣習です。
Rob、

2
#pragma clang diagnostic warning push変更を加える前にを使用して以前のコンパイラ設定状態#pragma clang diagnostic warning popを復元し、以前の状態を復元することもできます。ロードをオフにし、コード内にプラグマの行をたくさん有効にしたくない場合に便利です。
deanWombourne

次の行だけを無視しますか?
hfossli

70

奇妙だが真実:許容できる場合(つまり、結果が無効であり、runloopを1回循環させてもかまわない場合)、これがゼロであっても遅延を追加します。

[_controller performSelector:NSSelectorFromString(@"someMethod")
    withObject:nil
    afterDelay:0];

これにより警告が削除されます。これは、オブジェクトが返されず、なんらかの方法で誤って管理される可能性がないことをコンパイラに保証するためと考えられます。


2
これが実際に関連するメモリ管理の問題を解決するか、それとも同じ問題があるがXcodeはこのコードで警告するほど賢くないか知っていますか?
アーロンブラジャー2013年

これは意味的に同じではありません!performSelector:withObject:AfterDelay:を使用すると、runloopの次の実行でセレクターが実行されます。したがって、このメソッドはすぐに戻ります。
Florian

10
@フローリアンもちろん同じではありません!私の答えを読んでください:結果が無効であり、ランループが循環するため、許容できる場合は私が言います。それが私の答えの最初の文です。
マット

34

上記の回答に基づいて更新されたマクロは次のとおりです。これにより、returnステートメントでもコードをラップできます。

#define SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(code)                        \
    _Pragma("clang diagnostic push")                                        \
    _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")     \
    code;                                                                   \
    _Pragma("clang diagnostic pop")                                         \


SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(
    return [_target performSelector:_action withObject:self]
);

6
returnマクロ内にある必要はありません。return SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING([_target performSelector:_action withObject:self]);また、動作し、正気に見えます。
uasi 2013年

31

このコードには、コンパイラフラグや直接のランタイムコールは含まれません。

SEL selector = @selector(zeroArgumentMethod);
NSMethodSignature *methodSig = [[self class] instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setSelector:selector];
[invocation setTarget:self];
[invocation invoke];

NSInvocation複数の引数を設定できるため、performSelectorこれはどのメソッドでも機能します。


3
これが実際に関連するメモリ管理の問題を解決するか、それとも同じ問題があるがXcodeはこのコードで警告するほど賢くないか知っていますか?
アーロンブラジャー2013年

1
メモリ管理の問題を解決したと言えます。これは基本的に動作を指定できるためです。たとえば、呼び出しに引数を保持させるかどうかを選択できます。私の現在の知識では、それはあなたがあなたが何をしているのかを知っていて不正確なデータを提供しないことを信頼することによって現れるかもしれない署名の不一致の問題を修正しようとします。実行時にすべてのチェックを実行できるかどうかはわかりません。別のコメントで述べたように、mikeash.com / pyblog /…は、不一致によって何ができるかをうまく説明しています。
Mihai Timar

20

さて、ここにはたくさんの答えがありますが、これは少し異なるので、私が入れたと思ったいくつかの答えを組み合わせています。セレクターがvoidを返し、コンパイラーも抑制していることを確認するNSObjectカテゴリーを使用しています。警告。

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "Debug.h" // not given; just an assert

@interface NSObject (Extras)

// Enforce the rule that the selector used must return void.
- (void) performVoidReturnSelector:(SEL)aSelector withObject:(id)object;
- (void) performVoidReturnSelector:(SEL)aSelector;

@end

@implementation NSObject (Extras)

// Apparently the reason the regular performSelect gives a compile time warning is that the system doesn't know the return type. I'm going to (a) make sure that the return type is void, and (b) disable this warning
// See http://stackoverflow.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown

- (void) checkSelector:(SEL)aSelector {
    // See http://stackoverflow.com/questions/14602854/objective-c-is-there-a-way-to-check-a-selector-return-value
    Method m = class_getInstanceMethod([self class], aSelector);
    char type[128];
    method_getReturnType(m, type, sizeof(type));

    NSString *message = [[NSString alloc] initWithFormat:@"NSObject+Extras.performVoidReturnSelector: %@.%@ selector (type: %s)", [self class], NSStringFromSelector(aSelector), type];
    NSLog(@"%@", message);

    if (type[0] != 'v') {
        message = [[NSString alloc] initWithFormat:@"%@ was not void", message];
        [Debug assertTrue:FALSE withMessage:message];
    }
}

- (void) performVoidReturnSelector:(SEL)aSelector withObject:(id)object {
    [self checkSelector:aSelector];

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    // Since the selector (aSelector) is returning void, it doesn't make sense to try to obtain the return result of performSelector. In fact, if we do, it crashes the app.
    [self performSelector: aSelector withObject: object];
#pragma clang diagnostic pop    
}

- (void) performVoidReturnSelector:(SEL)aSelector {
    [self checkSelector:aSelector];

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self performSelector: aSelector];
#pragma clang diagnostic pop
}

@end

'v'を_C_VOIDに置き換えますか?_C_VOIDは<objc / runtime.h>で宣言されています。
Rik Renich

16

後世のために、帽子をリングに入れることにしました:)

最近、target/ selectorパラダイムから離れて、プロトコルやブロックなどを支持して、ますます再構築が見られます。しかし、performSelector私が今まで何度か使用したものに代わるドロップインが1つあります。

[NSApp sendAction: NSSelectorFromString(@"someMethod") to: _controller from: nil];

これらは、クリーンでARCセーフであり、とほとんど関係performSelectorなく使用できるとほぼ同じように置き換えられobjc_msgSend()ます。

ただし、iOSで利用可能なアナログがあるかどうかはわかりません。


6
これを含めてくれてありがとう。iOSで利用できます:[[UIApplication sharedApplication] sendAction: to: from: forEvent:]。一度調べてみましたが、動的な呼び出しを行うためだけにドメインまたはサービスの真ん中でUI関連のクラスを使用するのは少し不便に感じます。
Eduardo Scoz

2
え!!オーバーヘッドが大きくなり(メソッドが使用可能かどうかを確認し、使用できない場合はレスポンダーチェーンを上に移動する必要があるため)、エラー動作が異なります(レスポンダーチェーンを上に移動して何も見つからない場合はNOを返します)。単純にクラッシュするのではなく、メソッドに応答します)。idfrom-performSelector:...
tc

2
@tc。to:がnilでない限り、「レスポンダーチェーンを上に移動」しません。事前のチェックなしで、ターゲットオブジェクトに直接移動します。したがって、「オーバーヘッドが増える」ことはありません。それは素晴らしい解決策ではありませんが、あなたが与える理由は理由ではありません。:)
マット

15

このスレッドに関するMatt Gallowayの回答がその理由を説明しています

以下を検討してください。

id anotherObject1 = [someObject performSelector:@selector(copy)];
id anotherObject2 = [someObject performSelector:@selector(giveMeAnotherNonRetainedObject)];

では、ARCは、最初のオブジェクトが保持カウント1のオブジェクトを返し、2番目のオブジェクトが自動解放されたオブジェクトを返すことをどのようにして知ることができますか?

戻り値を無視する場合は、警告を抑制しても安全であると思われます。「それをしないでください」以外の方法で、performSelectorから保持されたオブジェクトを本当に取得する必要がある場合のベストプラクティスはわかりません。


14

@ c-roadは、ここに問題の説明がある正しいリンクを提供します。次に、performSelectorがメモリリークを引き起こす場合の私の例を示します。

@interface Dummy : NSObject <NSCopying>
@end

@implementation Dummy

- (id)copyWithZone:(NSZone *)zone {
  return [[Dummy alloc] init];
}

- (id)clone {
  return [[Dummy alloc] init];
}

@end

void CopyDummy(Dummy *dummy) {
  __unused Dummy *dummyClone = [dummy copy];
}

void CloneDummy(Dummy *dummy) {
  __unused Dummy *dummyClone = [dummy clone];
}

void CopyDummyWithLeak(Dummy *dummy, SEL copySelector) {
  __unused Dummy *dummyClone = [dummy performSelector:copySelector];
}

void CloneDummyWithoutLeak(Dummy *dummy, SEL cloneSelector) {
  __unused Dummy *dummyClone = [dummy performSelector:cloneSelector];
}

int main(int argc, const char * argv[]) {
  @autoreleasepool {
    Dummy *dummy = [[Dummy alloc] init];
    for (;;) { @autoreleasepool {
      //CopyDummy(dummy);
      //CloneDummy(dummy);
      //CloneDummyWithoutLeak(dummy, @selector(clone));
      CopyDummyWithLeak(dummy, @selector(copy));
      [NSThread sleepForTimeInterval:1];
    }} 
  }
  return 0;
}

この例でメモリリークを引き起こす唯一のメソッドは、CopyDummyWithLeakです。その理由は、ARCがcopySelectorが保持オブジェクトを返すことを知らないためです。

メモリリークツールを実行すると、次の画像が表示されます ここに画像の説明を入力してください 。...それ以外の場合はメモリリークはありません。 ここに画像の説明を入力してください


6

スコットトンプソンのマクロをより汎用的にするには:

// String expander
#define MY_STRX(X) #X
#define MY_STR(X) MY_STRX(X)

#define MYSilenceWarning(FLAG, MACRO) \
_Pragma("clang diagnostic push") \
_Pragma(MY_STR(clang diagnostic ignored MY_STR(FLAG))) \
MACRO \
_Pragma("clang diagnostic pop")

次に、次のように使用します。

MYSilenceWarning(-Warc-performSelector-leaks,
[_target performSelector:_action withObject:self];
                )

FWIW、私はマクロを追加しませんでした。誰かがそれを私の返答に追加しました。個人的には、私はマクロを使用しません。プラグマは、コード内の特殊なケースを回避するためにあり、プラグマは非常に明示的で、何が起こっているかを直接示します。マクロの背後に隠したり抽象化したりするよりも、それらをそのままにしておく方が好きですが、それは私だけです。YMMV。
Scott Thompson

@ScottThompsonそれは公正です。私にとっては、コードベース全体でこのマクロを検索するのは簡単です。また、根本的な問題に対処するために、通常、無音でない警告を追加します。
Ben Flynn、

6

警告を抑制しないでください!

コンパイラをいじくり回すには、少なくとも12の代替ソリューションがあります。
あなたが最初の実装時に賢い間、地球上のほとんどのエンジニアはあなたの足跡をたどることができません、そしてこのコードは最終的に壊れます。

安全なルート:

これらのソリューションはすべて機能しますが、元の意図とはある程度異なります。あなたがそう望むならそれparamが可能であると仮定してnilください:

安全なルート、同じ概念的な動作:

// GREAT
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:YES];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:YES modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:YES];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:YES modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

安全なルート、わずかに異なる動作:

この応答を参照)
代わりに任意のスレッドを使用します[NSThread mainThread]

// GOOD
[_controller performSelector:selector withObject:anArgument afterDelay:0];
[_controller performSelector:selector withObject:anArgument afterDelay:0 inModes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelectorInBackground:selector withObject:anArgument];

[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:NO];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:NO modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

危険なルート

壊れるに違いない、ある種のコンパイラのサイレンシングが必要です。現時点でSwiftで壊れていたことに注意してください。

// AT YOUR OWN RISK
[_controller performSelector:selector];
[_controller performSelector:selector withObject:anArgument];
[_controller performSelector:selector withObject:anArgument withObject:nil];

3
言い回しは非常に間違っています。安全なルートは危険なほど安全ではありません。警告を暗黙的に隠すため、間違いなく危険です。
ブライアンチェン

言葉遣いを侮辱しないように修正しますが、私は自分の言葉を受け入れます。サイレンシングの警告が許容できると思うのは、コードを所有していない場合のみです。すべての結果を理解せずに、沈黙したコードを安全に維持できるエンジニアはいません。これは、この議論を読むことを意味します。この方法は明らかに危険です。特に、12の単純な英語の強力な代替案を検討する場合は、
SwiftArchitect 2015

1
いいえ、私の得点はわかりませんでした。を使用することは警告を止める良い方法でperformSelectorOnMainThreadなく、副作用があります。(メモリリークは解決されません)エクストラ#clang diagnostic ignored は、非常に明確な方法で警告を明示的に抑制します。
ブライアンチェン

- (void)メソッドでセレクターを実行することが本当の問題であることは真です。
SwiftArchitect 2016年

そして、これを通じて複数の引数を持つセレクターをどのように呼び出し、同時に安全ですか?@SwiftArchitect
カタリン

4

ARCを使用しているため、iOS 4.0以降を使用している必要があります。つまり、ブロックを使用できます。セレクターを覚えて実行する代わりにブロックを取った場合、ARCは実際に何が行われているのかをより適切に追跡でき、誤ってメモリリークを引き起こすリスクを冒す必要はありません。


実際、ブロックは、ARCが解決しない保持サイクルを誤って作成することを非常に簡単にします。私が暗黙的にselfivarを介して(たとえばのivar代わりにself->ivar)使用したときにコンパイラ警告が表示されたことを願っています。
tc。

-Wimplicit-retain-self?
OrangeDog 2015

2

ブロックアプローチを使用する代わりに、いくつかの問題が発生しました。

    IMP imp = [_controller methodForSelector:selector];
    void (*func)(id, SEL) = (void *)imp;

次のように、NSInvocationを使用します。

    -(void) sendSelectorToDelegate:(SEL) selector withSender:(UIButton *)button 

    if ([delegate respondsToSelector:selector])
    {
    NSMethodSignature * methodSignature = [[delegate class]
                                    instanceMethodSignatureForSelector:selector];
    NSInvocation * delegateInvocation = [NSInvocation
                                   invocationWithMethodSignature:methodSignature];


    [delegateInvocation setSelector:selector];
    [delegateInvocation setTarget:delegate];

    // remember the first two parameter are cmd and self
    [delegateInvocation setArgument:&button atIndex:2];
    [delegateInvocation invoke];
    }

1

引数を渡す必要がない場合、簡単な回避策はを使用することvalueForKeyPathです。これはClassオブジェクトでも可能です。

NSString *colorName = @"brightPinkColor";
id uicolor = [UIColor class];
if ([uicolor respondsToSelector:NSSelectorFromString(colorName)]){
    UIColor *brightPink = [uicolor valueForKeyPath:colorName];
    ...
}

-2

ここでプロトコルを使用することもできます。したがって、次のようなプロトコルを作成します。

@protocol MyProtocol
-(void)doSomethingWithObject:(id)object;
@end

セレクターを呼び出す必要があるクラスでは、@ propertyがあります。

@interface MyObject
    @property (strong) id<MyProtocol> source;
@end

@selector(doSomethingWithObject:)MyObjectのインスタンスを呼び出す必要がある場合は、次のようにします。

[self.source doSomethingWithObject:object];

2
ねえ、ありがとう、でもNSSelectorFromStringを使用するポイントは、実行時に呼び出すセレクタがわからないときです。
Eduardo Scoz 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.