解決
コンパイラは、これについて何らかの理由で警告しています。この警告が単に無視されることは非常にまれであり、簡単に回避できます。方法は次のとおりです。
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>
)。これらの関数ポインターは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ではランタイムが、呼び出しているメソッドの結果をどう処理するかを知る必要があるためです。結果は何もすることができますvoid
、int
、char
、NSString *
、id
、などARCは通常、あなたが作業しているオブジェクトタイプのヘッダからこの情報を取得します。3
ARCが戻り値について考慮すべきことは、実際には4つだけです:4
- 非オブジェクト型(無視する
void
、int
など)
- オブジェクト値を保持し、使用されなくなったら解放します(標準の想定)
- 使用されなくなったときに新しいオブジェクト値を解放します(
init
/ copy
ファミリのメソッド、またはで属性付けされたメソッドns_returns_retained
)
- 何もせず、返されたオブジェクト値がローカルスコープで有効であると想定します(最も内側のリリースプールが排出され、で属性が付与されるまで
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では利用できなくなりました。
時とともに、私たちはこの進歩を見てきました:
- Objective-Cの初期バージョンでは
performSelector:
(手動メモリ管理)が可能です
- ARCを備えたObjective-Cは、
performSelector:
- Swiftは
performSelector:
これらのメソッドへのアクセス権を持たず、「本質的に安全ではない」と文書化しています
ただし、名前付きセレクタに基づいてメッセージを送信するという考えは、「本質的に安全ではない」機能ではありません。このアイデアは、Objective-Cや他の多くのプログラミング言語で長い間成功裏に使用されてきました。
1すべてのObjective-Cメソッドには2つの隠し引数がself
あり_cmd
、それらはメソッドを呼び出すときに暗黙的に追加されます。
2NULL
Cでは関数の呼び出しは安全ではありません。コントローラーの存在を確認するために使用されるガードは、オブジェクトがあることを確認します。したがって、IMP
fromを取得することがわかりますmethodForSelector:
(_objc_msgForward
メッセージ転送システムへのエントリである場合もあります)。基本的に、ガードを配置すると、呼び出す関数があることがわかります。
3実際には、オブジェクトを次のように宣言id
し、すべてのヘッダーをインポートしていない場合、誤った情報を取得する可能性があります。コンパイラが問題ないと考えるコードでクラッシュが発生する可能性があります。これは非常にまれですが、発生する可能性があります。通常、2つのメソッドシグネチャのどちらから選択するかがわからないという警告が表示されます。
4上でARC基準参照保持戻り値と保持されない戻り値の詳細については。