解決
コンパイラは、これについて警告しています。この警告を単に無視する必要があることは非常にまれであり、回避するのは簡単です。方法は次のとおりです。
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
がに応答しますが、Objective-CランタイムでmethodForSelector:
使用することもできclass_getMethodImplementation
ます(のようなプロトコル参照しかない場合に便利ですid<SomeProto>
)。これらの関数ポインタはIMP
sと呼ばれ、単純なtypedef
ed関数ポインタ(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では、ランタイムが呼び出しているメソッドの結果をどう処理するかを知る必要があるためです。結果は次のようになります:、、、、、など。ARCは通常、操作しているオブジェクトタイプのヘッダーからこの情報を取得しvoid
ます。3int
char
NSString *
id
ARCが戻り値として考慮するのは実際には4つだけです。4
- 非オブジェクトタイプ(、、
void
などint
)を無視する
- オブジェクト値を保持し、使用されなくなったら解放します(標準の仮定)
- 使用されなくなったときに新しいオブジェクト値を解放します(
init
/copy
ファミリーのメソッドまたは属性付きのメソッドns_returns_retained
)
- 何もせず、返されたオブジェクト値がローカルスコープで有効であると想定します(最も内側のリリースプールがドレインされるまで、に起因します
ns_returns_autoreleased
)
tomethodForSelector:
の呼び出しは、呼び出しているメソッドの戻り値がオブジェクトであると想定しますが、それを保持/解放しません。したがって、上記の#3のようにオブジェクトが解放されることになっている場合(つまり、呼び出しているメソッドが新しいオブジェクトを返す場合)、リークが発生する可能性があります。
そのreturnvoid
または他の非オブジェクトを呼び出そうとしているセレクターの場合、コンパイラー機能が警告を無視できるようにすることができますが、それは危険な場合があります。Clangが、ローカル変数に割り当てられていない戻り値を処理する方法を数回繰り返しているのを見てきました。methodForSelector:
ARCを有効にすると、使用したくない場合でも、返されたオブジェクト値を保持および解放できない理由はありません。コンパイラの観点からは、それは結局のところオブジェクトです。つまり、呼び出しているメソッドがsomeMethod
非オブジェクト(を含むvoid
)を返している場合、ガベージポインタ値が保持/解放されてクラッシュする可能性があります。
追加の引数
1つの考慮事項は、これはで発生するのと同じ警告でperformSelector:withObject:
あり、そのメソッドがパラメーターをどのように消費するかを宣言しないと、同様の問題が発生する可能性があることです。ARCでは、消費されたパラメーターを宣言できます。メソッドがパラメーターを消費した場合、最終的にはゾンビにメッセージを送信してクラッシュします。ブリッジキャストでこれを回避する方法はいくつかありますが、実際には、IMP
上記の関数ポインタの方法論を使用する方がよいでしょう。消費されたパラメータが問題になることはめったにないので、これが発生する可能性は低いです。
静的セレクター
興味深いことに、コンパイラーは静的に宣言されたセレクターについて文句を言いません。
[_controller performSelector:@selector(someMethod)];
これは、コンパイラーがコンパイル中にセレクターとオブジェクトに関するすべての情報を実際に記録できるためです。何かについて何も仮定する必要はありません。(私は1年前にソースを見てこれをチェックしましたが、現在は参照がありません。)
抑制
この警告の抑制が必要であり、優れたコード設計が必要となる状況を考えようとすると、私は空白になります。この警告を消音する必要がある(そして上記は物事を適切に処理しない)経験があった場合は、誰かが共有してください。
もっと
これを処理するためにを構築することも可能NSMethodInvocation
ですが、そうすることはより多くのタイピングを必要とし、また遅いので、それを行う理由はほとんどありません。
歴史
performSelector:
メソッドファミリーがObjective-Cに最初に追加されたとき、ARCは存在しませんでした。ARCの作成中に、Appleは、名前付きセレクタを介して任意のメッセージを送信するときにメモリの処理方法を明示的に定義する他の手段を使用するように開発者をガイドする方法として、これらのメソッドに対して警告を生成する必要があると判断しました。Objective-Cでは、開発者は生の関数ポインターでCスタイルのキャストを使用してこれを行うことができます。
Swiftの導入により、Appleは一連のメソッドを「本質的に安全ではない」と文書化し、 Swiftでは使用できなくなりました。performSelector:
時間の経過とともに、この進行が見られました。
- Objective-Cの初期のバージョンでは、
performSelector:
(手動メモリ管理)が可能です
- ARCを使用したObjective-Cは、
performSelector:
- Swiftは、これらのメソッドにアクセスできず
performSelector:
、「本質的に安全ではない」と文書化しています。
ただし、名前付きセレクターに基づいてメッセージを送信するという考え方は、「本質的に安全でない」機能ではありません。このアイデアは、Objective-Cや他の多くのプログラミング言語で長い間成功裏に使用されてきました。
1すべてのObjective-Cメソッドには2つの隠し引数がself
あり_cmd
、メソッドを呼び出すと暗黙的に追加されます。
2 Cでは、関数の呼び出しNULL
は安全ではありません。コントローラーの存在を確認するために使用されるガードは、オブジェクトがあることを確認します。IMP
したがって、から取得することはわかっていますmethodForSelector:
(ただし_objc_msgForward
、メッセージ転送システムへのエントリである可能性があります)。基本的に、ガードが配置されていれば、呼び出す関数があることがわかります。
3実際には、オブジェクトをとして宣言id
し、すべてのヘッダーをインポートしていない場合、間違った情報を取得する可能性があります。コンパイラが問題ないと判断したコードがクラッシュする可能性があります。これは非常にまれですが、発生する可能性があります。通常、2つのメソッドシグネチャのどちらから選択するかがわからないという警告が表示されます。
4詳細については、保持された戻り値と保持されていない戻り値に関するARCリファレンスを参照してください。