7

objc_msgSendメソッドを使用して、いくつかのメソッドを動的に呼び出そうとしています。クラス A からクラス B のいくつかのメソッドを呼び出したいとします。クラス B には次のような 2 つのメソッドがあります。

- (void) instanceTestWithStr1:(NSString *)str1 str2:(NSString *)str1;
+ (void) methodTestWithStr1:(NSString *)str1 str2:(NSString *)str1;

そして、クラス A で次のようにクラス メソッドを正常に呼び出すことができます。

objc_msgSend(objc_getClass("ClassB"), sel_registerName("methodTestWithStr1:str2:"), @"111", @"222");

そして、クラス A でもこのようにインスタンス メソッドを正常に呼び出すことができます。

objc_msgSend([[objc_getClass("ClassB") alloc] init], sel_registerName("instanceTestWithStr1:str2:"), @"111", @"222");

ただし、クラス BI のインスタンスを取得するには、「init」の代わりに「initWithXXXXX:XXXXXX:XXXXXX」を呼び出さなければならないため、必要なパラメーターをクラス B に渡して初期化を行う必要があります。そこで、クラス A に ClassB のインスタンスを変数として格納しました。

そして、次のようにメソッドを呼び出します (成功):

問題は、「ClassName」や「SEL」などのクラス名とメソッド sel を適用するだけでメソッドを呼び出し、それを動的に呼び出したいことです。

  1. クラスメソッドなら。次に、次のように呼び出します: objc_msgSend(objc_getClass("ClassName"), sel_registerName("SEL"));

  2. インスタンス メソッドの場合は、呼び出し元のクラスで既存のクラス インスタンス変数を見つけます。 objc_msgSend([self.classInstance, sel_registerName("SEL"));

したがって、次の方法があるかどうかを知りたいです。

  1. クラスに特定のメソッドがあるかどうかを確認します(「responseToSelector」がそのメソッドになることがわかりました)

  2. クラスメソッドまたはインスタンスメソッドの特定のメソッドを確認します(おそらくresponseToSelector同様に使用できます)

  3. クラスが特定のクラスのインスタンス変数を持っているかどうかを確認するため、次のようなインスタンス メソッドを呼び出すことができます。
4

2 に答える 2

8

あなたはおそらくこれを読みたいと思うでしょう。あなたが求めているのは事実上「新しいディスパッチャーを作りたい」ということであり、その質問に答えるには、既存のディスパッチャーがどのように機能するかを完全に理解している必要があります。

それがあなたのしていることだと教えてください。言語間の橋渡し?そうでない場合は、うさぎの穴の奥深くにいることになり、探索するのは非常に興味深いものになりますが、非常に効率的でもエレガントなソリューションでもない可能性があります。

今:

問題は、「ClassName」や「SEL」などのクラス名とメソッド sel を適用するだけでメソッドを呼び出し、それを動的に呼び出したいということです。

  1. クラスメソッドなら。次に、次のように呼び出します: 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);

明らかに、引数が必要な場合は、以下を参照してください。

したがって、次の方法があるかどうかを知りたいです。

  1. クラスに特定のメソッドがあるかどうかを確認します(「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]);
}

これは、一連の巧妙なランタイム コードを記述するよりも、デバッグがはるかに簡単であることが証明されています。

于 2012-09-19T05:54:32.487 に答える
3

さて、これが私の初歩的な実装です。それはかなりのことを前提としています:

  • すべてのオブジェクトはサブクラス化する必要がありNSObjectます。限目。そうしないと、後で問題が発生します-methodSignatureForSelector
  • すべてのメソッドには有効な署名が必要です。つまり、私のようなランタイム ハッカーは、メソッドを動的に追加することに関しては、ある種の失敗を犯しており、最初に調査を行う必要があります。
  • プリミティブへのメッセージの送信に問題がないことを前提としています (KVC によって提供される自動ボクシングを使用して、たとえばdoubleに昇格されNSNumberます) 。
  • 関数に渡されるプリミティブ引数はサポートされていません (したがって、ここではオブジェクトのみ、またはブリッジに夢中になりたい場合はポインターのみ)
  • また、可変長関数もサポートしていません ( の制限NSInvocation)。va_listこれを行いたい場合は、 を取り、代わりにそれらを使用する関数のバージョンを探してみてください。
  • プロパティではなく iVar のみをチェックしますが、@synthesize'd プロパティはすでにリストに含まれている必要があります。

以下にコンパイル時の注意事項を示します。

  • ARC を有効にする必要があります。私はもう ARC 以外のコードを書いていないので、誰かがこれを試してバックポートしたいなら、彼らは私のゲストになることができます。
  • C99 VLA も必要です。ARC Objective-C をコンパイルしているコンパイラには既にこれがあるはずです (実際、clang は ARC をサポートし、実際に C99 VLA をサポートする唯一のコンパイラだと思います) malloc。 .
  • iOS アーキテクチャ用にコンパイルした場合、これはテストされていません。私は Mac OS を使用してのみテストしましたが、ここで使用している方法はiOS でも使用できるはずです。そうでない場合はお知らせください。修正します。

NSNumberこれ以上苦労することなく、コードは次のとおりです (テスト用にいくつかのカテゴリを追加しましたNSStringが、これらはこのコードの目的には関係ありません)。

#import <objc/runtime.h>

@interface NSObject(dynamicSELlookup)

-(void) performSelectorOnClassOrIvar:(Class) cls selector:(SEL) selector arguments:(NSArray *) args;

@end

@implementation NSObject (dynamicSELlookup)

-(void) performSelectorOnClassOrIvar:(Class) cls selector:(SEL) selector arguments:(NSArray *) args
{
    // we must copy to a C-array so we can take adresses. here we use C99's VLAs, so we don't have to free anything
    __unsafe_unretained id argsArray[args.count];
    [args getObjects:argsArray];

    // if its a static method, then our job is simple. create a NSInvocation from our arguments, and send it on it's way
    if ([cls respondsToSelector:selector])
    {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[cls methodSignatureForSelector:selector]];

        for (int i = 0; i < args.count; i++)
        {
            // notice the '+ 2' here. this is because there are two 'hidden' arguments to an objective-c message call - '_cmd' & 'self'.
            [invocation setArgument:&argsArray[i] atIndex:i + 2];
        }

        // set the selector of the invocation, and fire it off!
        [invocation setSelector:selector];
        [invocation invokeWithTarget:cls];
        return;
    }

    // otherwise loop through all the iVars.
    unsigned iVarCount = 0;
    Ivar *iVars = class_copyIvarList([self class], &iVarCount);

    for (int i = 0; i < iVarCount; i++)
    {
        // We are going to use KVC here, so we can auto-box our return values (thus it works for primitives too)
        id value = [self valueForKey:@(ivar_getName(iVars[i]))];

        // make sure the target class is OK, and that we respond to the selector
        if ([value isKindOfClass:cls] && [value respondsToSelector:selector])
        {
            // just like before, we create our invocation
            NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[value methodSignatureForSelector:selector]];

            for (int i = 0; i < args.count; i++)
            {
                // notice the '+ 2' here. this is because there are two 'hidden' arguments to an objective-c message call - '_cmd' & 'self'. 
                [invocation setArgument:&argsArray[i] atIndex:i + 2];
            }

            // set the selector of the invocation, and fire it off!
            [invocation setSelector:selector];
            [invocation invokeWithTarget:value];
            // uncomment the below line if you only want to execute on the first target found
            // break;
        }
    }

    free(iVars);
}

@end

@interface MyObject : NSObject
{
    @public
    int someIntegerVar;
    double someDoubleVar;

    NSObject *someObjectVar;
}

@end

@implementation MyObject
@end

@implementation NSNumber(print)

+(void) classMethod
{
    NSLog(@"Hey, I'm a class method!");
}

// simple category for showing ivars off
-(void) printValue
{
    NSLog(@"%@", self);
}

-(void) printValueWithArg:(id) argument
{
    NSLog(@"%@ - %@", self, argument);
}

@end

@implementation NSString (print)

-(void) print
{
    NSLog(@"%@", self);
}

-(void) printFormat:(id) arg
{
    NSLog(self, arg);
}

@end

// Sample Usage
int main()
{
    @autoreleasepool
    {
        MyObject *obj = [MyObject new];
        obj->someDoubleVar = M_PI;
        obj->someIntegerVar = 5;
        obj->someObjectVar = @"hello there, %@";

        [obj performSelectorOnClassOrIvar:[NSNumber class] selector:@selector(printValue) arguments:nil];
        [obj performSelectorOnClassOrIvar:[NSNumber class] selector:@selector(classMethod) arguments:nil];
        [obj performSelectorOnClassOrIvar:[NSNumber class] selector:@selector(printValueWithArg:) arguments:@[ @"Hello" ]];
        [obj performSelectorOnClassOrIvar:[NSString class] selector:@selector(print) arguments:nil];
        [obj performSelectorOnClassOrIvar:[NSString class] selector:@selector(printFormat:) arguments:@[ @"Richard J Ross III"]];
    }
}

出力:

2012-09-19 00:05:07.150 TestProj[8592:303] 5
2012-09-19 00:05:07.152 TestProj[8592:303] 3.141592653589793
2012-09-19 00:05:07.152 TestProj[8592:303] ねえ、私はクラス メソッドです!
2012-09-19 00:05:07.153 TestProj[8592:303] 5 - こんにちは
2012-09-19 00:05:07.153 TestProj[8592:303] 3.141592653589793 - こんにちは
2012-09-19 00:05:07.154 TestProj[8592:303] こんにちは、%@
2012-09-19 00:05:07.154 TestProj[8592:303] こんにちは、リチャード J ロス III
于 2012-09-19T04:13:19.480 に答える