[解決済み] performSelectorのセレクタが不明なため、リークが発生する可能性があります。
質問
ARCコンパイラで以下のような警告が表示されるのですが。
"performSelector may cause a leak because its selector is unknown".
こんな感じです。
[_controller performSelector:NSSelectorFromString(@"someMethod")];
なぜこのような警告が出るのでしょうか?セレクタが存在するかどうかをコンパイラがチェックできないことは理解できますが、なぜそれがリークの原因になるのでしょうか? また、この警告が出ないようにするには、どのようにコードを変更すればよいのでしょうか?
解決方法は?
解決方法
コンパイラが警告するのには理由があります。この警告を無視することは非常に稀であり、回避することは簡単です。以下にその方法を説明します。
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
であり、単純な
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
など) - オブジェクトの値を保持し、使われなくなったら解放する(標準的な前提)。
-
使用されなくなった新しいオブジェクトの値を解放する(MODE)
init
/copy
を持つファミリーまたはアトリビュートns_returns_retained
) -
何もしない & 返されたオブジェクトの値はローカルスコープで有効であると仮定します(最も内側のリリースプールが枯渇するまで、属性は
ns_returns_autoreleased
)
の呼び出しは
methodForSelector:
は、呼び出したメソッドの戻り値がオブジェクトであると仮定し、それを保持/解放しません。そのため、上記の#3のようにオブジェクトが解放されることになっている場合(つまり、呼び出したメソッドが新しいオブジェクトを返す場合)には、リークを発生させてしまう可能性があります。
を返すセレクタを呼ぼうとしている場合、そのセレクタは
void
などのオブジェクトでないものは、コンパイラの機能を有効にして警告を無視することも可能ですが、危険かもしれません。Clangがローカル変数に代入されていない戻り値をどう扱うか、何度か繰り返しているのを見たことがあります。ARCを有効にして、オブジェクトの値を保持したり解放したりできない理由はありません。
methodForSelector:
を使いたくもないのに。コンパイラから見れば、結局はオブジェクトなのだ。つまり、もしあなたが呼んでいるメソッドが
someMethod
を含む)、非オブジェクトを返している。
void
) の場合、ガベージポインタの値が保持/解放され、クラッシュする可能性があります。
追加引数
を使用した場合にも同じ警告が発生することを考慮しています。
performSelector:withObject:
そのメソッドがどのようにパラメータを消費するかを宣言していないと、同様の問題が発生する可能性があります。ARCでは
消費されるパラメータ
メソッドがパラメータを消費してしまうと、最終的にはゾンビにメッセージを送ってクラッシュしてしまうでしょう。ブリッジキャストでこれを回避する方法もありますが、実際には、単純に
IMP
と関数ポインタの方法論を紹介しました。消費されるパラメータが問題になることはほとんどないので、このような問題は発生しないと思われます。
静的セレクタ
興味深いことに、コンパイラは静的に宣言されたセレクタについて文句を言いません。
[_controller performSelector:@selector(someMethod)];
この理由は、コンパイラがコンパイル時にセレクタとオブジェクトに関するすべての情報を実際に記録することができるためです。コンパイラは何も仮定する必要がないのです。(これは1年ほど前にソースを見て確認したのですが、今はリファレンスがありません)。
サプレッション
この警告を抑制することが必要であり、良いコードデザインであるような状況を考えようとすると、空白になってしまいます。どなたか、この警告の抑制が必要だった(そして、上記では適切に処理できない)経験があれば、教えてください。
もっと見る
を構築することが可能です。
NSMethodInvocation
を使うこともできますが、そうすると入力が多くなり、速度も遅くなるので、そうする理由はあまりありません。
歴史
このとき
performSelector:
このメソッド群が最初にObjective-Cに追加されたとき、ARCは存在しませんでした。AppleはARCを作成する際に、名前付きセレクタで任意のメッセージを送信する際に、メモリをどのように処理すべきかを明示的に定義する他の手段を使うよう開発者を誘導する方法として、これらのメソッドに対して警告を発生させるべきであると判断したのです。Objective-C では、開発者は生の関数ポインタに C スタイルのキャストを使用することで、これを実現することができます。
Swiftの導入に伴い、Appleは
が文書化されています。
は
performSelector:
ファミリーのメソッドは本質的に安全でないため、Swift では使用できません。
時を経て、私たちはこのような経過をたどってきました。
-
Objective-Cの初期バージョンでは
performSelector:
(手動メモリ管理) -
Objective-CでARCの利用を警告する
performSelector:
-
Swiftは
performSelector:
で、これらのメソッドは本質的に安全でないものとして文書化されています。
しかし、名前付きセレクタに基づいてメッセージを送信するというアイデアは、"本質的に安全でない"機能ではありません。この考え方は、Objective-Cや他の多くのプログラミング言語で長い間うまく利用されてきました。
1
Objective-Cのメソッドはすべて、2つの隠し引数を持っています。
self
と
_cmd
は、メソッドを呼び出したときに暗黙のうちに追加されるものです。
2
を呼び出すと
NULL
コントローラの存在を確認するためのガードにより、オブジェクトが存在することが確認されます。そのため
IMP
から
methodForSelector:
(である可能性もありますが)。
_objc_msgForward
メッセージ転送システムへのエントリー)。基本的に、ガードがあることで、呼び出すべき関数があることがわかる。
3
実は、オブジェクトを以下のように宣言すると、間違った情報を取得する可能性があります。
id
で、すべてのヘッダをインポートしていない場合。コンパイラが問題ないと思っているコードで、クラッシュしてしまう可能性があります。これは非常に稀なことですが、起こり得ます。通常は、2つのメソッドシグネチャのどちらを選べばいいのかわからないという警告が表示されるだけでしょう。
関連
-
XCode のコンパイル例外を解決する clang: error: linker command failed with exit code 1
-
IOSラーニングノート「このクラスはxxxのキーバリューコーディングに対応していません」問題解決
-
[解決済み] Xcode 6.3 - 現在の iOS Development 証明書または保留中の証明書要求がすでにあります。
-
[解決済み] iOSまたはmacOSで、インターネット接続が有効かどうかを確認するにはどうすればよいですか?
-
[解決済み] iOSシミュレータでスクリーンショットを撮る
-
[解決済み] テキストフィールドを移動する方法(次へボタン/完了ボタン)
-
[解決済み] iphoneアプリのベータテストはどのように行うのですか?
-
[解決済み] コア・データ エンティティの全インスタンスを削除する最短の方法
-
[解決済み] Swift 3でディスパッチキューを作成する方法
-
[解決済み] iPhoneでナビゲーションバーを1ページ目だけ非表示にする
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
dyld: ライブラリがロードされていません。エラーの解決
-
[解決済み] Objective-Cで、ある文字列が他の文字列を含んでいるかどうかを調べるにはどうすればよいですか?
-
[解決済み] UIViewController のビューが表示されているかどうかを確認する方法
-
[解決済み] UIViewに角丸をつける
-
[解決済み] iOSシミュレータでスクリーンショットを撮る
-
[解決済み] UILabelで複数行のテキストを表示する
-
[解決済み] iOS 7でステータスバーとナビゲーションバーがビューの境界を越えて表示される
-
[解決済み] Swift で HTTP リクエストを行うにはどうしたらいいですか?
-
[解決済み] UITableView - トップにスクロールする
-
[解決済み] 未宣言のセレクタ」の警告を消す方法