あなたはおそらくこれを読みたいと思うでしょう。あなたが求めているのは事実上「新しいディスパッチャーを作りたい」ということであり、その質問に答えるには、既存のディスパッチャーがどのように機能するかを完全に理解している必要があります。
それがあなたのしていることだと教えてください。言語間の橋渡し?そうでない場合は、うさぎの穴の奥深くにいることになり、探索するのは非常に興味深いものになりますが、非常に効率的でもエレガントなソリューションでもない可能性があります。
今:
問題は、「ClassName」や「SEL」などのクラス名とメソッド sel を適用するだけでメソッドを呼び出し、それを動的に呼び出したいということです。
- クラスメソッドなら。次に、次のように呼び出します: objc_msgSend(objc_getClass("ClassName"), sel_registerName("SEL"));
Class klass = objc_getClass("ClassName"); // NSClassFromString(@"ClassName")
SEL sel = sel_getUID("selector"); // NSSelectorFromString(@"selector");
if ( [klass respondsToSelector:sel] )
objc_msgSend(klass, sel);
渡したい引数がある場合は、以下を参照してください。 NSInvocation
リチャードの答えは高レベルのアプローチですが、間接的な使用ですobjc_msgSend()
(NSInvocationには制限があります)。
「2」。インスタンス メソッドの場合は、呼び出し元のクラスで既存のクラス インスタンス変数を見つけます。 objc_msgSend([self.classInstance, sel_registerName("SEL"));
それは意味がありません。クラスにはインスタンス変数がありません。クラスのインスタンスにはインスタンス変数がありますが、この 1 つの場所で作成するランダムなインスタンスではなく、特定のインスタンスが必要になる可能性があります。インスタンスは状態を保持し、時間の経過とともにその状態を増加させます。
いずれにせよ、上記のメカニズムを使用してクラスのメソッドを簡単に呼び出すことができますclassInstance
(これは完全に無意味です。単に記述[self classInstance]
して実行するだけです)。
id classInstance = [self classInstance];
SEL sel = ... get yer SEL here ...;
if ([classInstance respondsToSelector:sel])
objc_msgSend(classInstance, sel);
明らかに、引数が必要な場合は、以下を参照してください。
したがって、次の方法があるかどうかを知りたいです。
- クラスに特定のメソッドがあるかどうかを確認します(「responseToSelector」がそのメソッドになることがわかりました)
上記を参照。クラスは に応答しrespondsToSeletor:
ます。クラスのインスタンスがセレクターに応答するかどうかを確認したい場合は、 を呼び出すことができますinstancesRespondToSelector:
。
Class klass = ... get yer class on...;
SEL someSelector = ... get that SEL ...;
if ([klass instancesRespondToSelector:someSelector])
objc_msgSend(instanceOfKlassObtainedFromSomewhere, someSelector);
また議論?下記参照。
「2」。クラスメソッドまたはインスタンスメソッドの特定のメソッドを確認します(おそらくresponseToSelector
同様に使用できます)
上記を参照。クラスが与えられた場合、クラスまたはインスタンスが特定のセレクターに応答するかどうかを確認します。NSObject プロトコルの多くのセレクターでは、クラスが NSObject インスタンス メソッドの多くに応答することに注意してください。これは、メタ クラス(クラスがインスタンスであるクラス) がこれらのメソッドのかなりの数を実装しているためです。
「3」。クラスが特定のクラスのインスタンス変数を持っているかどうかを確認するため、次のようなインスタンス メソッドを呼び出すことができます。
setter/getter メソッドとインスタンス変数の関係は完全に偶然です。ivar が存在する必要はなく、特定の ivar に対してセッターやゲッターが存在する必要もありません。したがって、ivar 名に基づいて任意にメソッドを呼び出すと失敗することが多いため、この質問は意味がありません。
リチャードが示唆するように、キー値コーディングを使用できますが、それはセッターに渡された値の手動ボックス化と、非オブジェクト型のゲッターから取得された値の手動ボックス化解除を意味します。
内部的には、KVC はヒューリスティックを実装して、要求された名前とほぼ一致する名前を持つメソッドまたは ivar をクラスで検索します。主に _ プレフィックスの検索などを行うためです。NSKeyValueCoding.h ヘッダーは興味深い読み物です。
いずれにせよ、セレクターは必要ありません。名前を指定して、次のようにします。
id foo = [myInstance valueForKey:@"iVarName"];
と:
[myInstance setValue:[NSNumber numberWithInt:42] forKey:@"ivarName"];
明らかに、タイピングは主要な問題です。オブジェクト以外のタイプがある場合は、NSValue コンテナの内外に対処する必要があり、すべてが適合するとは限らないため、KVC メソッド/ivar 検索アルゴリズムをリバース エンジニアリングする必要があります (そうではありません)。ハード -- 文字列操作とルックアップの束) を実行し、以下のように任意の引数を渡します。
どちらも明示的な引数型を持つ非 varargs フォームへのobjc_msgSend()
型キャストではないため、両方の呼び出しが技術的に間違っていることに注意してください。objc_msgSend()
次のようなものが必要です。
// - (void) instanceTestWithStr1:(NSString *)str1 str2:(NSString *)str1;
void (*msgSendVoidStrStr(id, SEL, NSString*, NSString*) = (void*)objc_msgSend;
msgSendVoidStrStr(...obj..., @selector(instanceTestWithStr1:str2:), str1, str2);
これは、varargs ABI と明示的な引数型の ABI が必ずしもすべてのアーキテクチャで互換性があるとは限らないためです。ARC、IIRC は、これを明示的に実施します。
また、インスタンス メソッドを呼び出すとその場でクラスのインスタンスがインスタンス化される、クラスまたはインスタンス メソッドを任意に呼び出すという概念は、あまり意味がないことにも注意してください。しかし、ちょっと... あなたのコード。
また、そのような方法で呼び出したくないことに注意してくださいsel_registerName()
。セレクターを呼び出そうとしている場合は、既に存在しているほうがよいでしょう。その関数は、実行時にクラスを定義するために明示的に存在します。NSSelectorFromString()
orを使用するのが最善です (残念ながら、何年にもわたる規律のないプログラマーのためにsel_getUid()
、これは事実上呼び出しになります)。sel_registerName()
少なくともあなたの意図は正しいでしょう。
さて、objc_msgSend()
あなたが望むように使用するには、結果の答えが根本的に異なる1つの質問に答える必要があります. 1 つの答えは、「ああ、ただ X を実行してください」という簡単な方法です。
質問:メソッド シグネチャの固定セットがありますか、それとも多くの型の引数の任意のセットを渡す必要がありますか?
最終的に、コードがどれほど複雑になるかは、さまざまな種類の引数の数と数によって決まります。 引数が 0、1、または 2 つしかなく、それらが常にオブジェクトである場合はinvokeSelector:
、invokeSelector:withObject:
と を使用してinvokeSelector:withObject:withObject:
ください。
答えが「メソッド シグネチャの固定セット」である場合、答えは上記のとおりです。使用したいすべての可能なメソッド シグネチャを含む関数ポインタを宣言し、実行時に適切なものを選択して、上記のように関数呼び出しとして呼び出すだけです。
ここで、答えが「引数のさまざまな組み合わせを持つセレクターの任意のセット」である場合、答えははるかに困難です。libffi (またはそれに似たもの) を使用して、コンパイラがコンパイル時に行うことをプログラムで行う必要がありますmsgSendVoidStrStr(...obj..., @selector(instanceTestWithStr1:str2:), str1, str2);
。 libffi
ほぼ任意の引数と戻り値の型で呼び出しをエンコードするために必要なすべてを提供します。
使いにくいです。実際、libffi を使用して独自のスタック フレームを構築するのは非常に難しいため、呼び出しのすべての可能な組み合わせをダンプし、組み合わせごとにカバー関数を作成するスクリプトを作成する方が簡単かもしれません。引数をNSArray*
コンテナーとして取得し、内部でデコードする可能性があります。 . (自動生成) のようなもの:
void msgSendVoidStrStr(id obj, SEL _cmd, NSArray*args) {
objc_msgSend(obj, _cmd, [args objectAtIndex:0], [args objectAtIndex:1]);
}
これは、一連の巧妙なランタイム コードを記述するよりも、デバッグがはるかに簡単であることが証明されています。