9

私はメソッドの入れ替えをいじっていて、実行後に元の関数を呼び出したいmethod_exchangeImplementations. このためにセットアップした2つのプロジェクトがあります。

最初のプロジェクトは、アプリケーションのメイン プロジェクトです。このプロジェクトには、アプリケーションのすべてのロジックが含まれています。originalMethodNameビューが読み込まれるときに呼び出されることに注意してください。

@implementation ViewController

- (void)originalMethodName 
{
    NSLog(@"REAL %s", __func__);
}

- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"REAL %s", __func__);

    [self originalMethodName];
}

@end

2 番目のプロジェクトには、スウィズリングのコードのみが含まれています。関数を呼び出しswizzle_originalMethodNameてメインアプリケーションに挿入したいコードを含むメソッドがあります。originalMethodName

@implementation swizzle_ViewController

- (void)swizzle_originalMethodName
{
    NSLog(@"FAKE %s", __func__);
}

__attribute__((constructor)) static void initializer(void)
{
    NSLog(@"FAKE %s", __func__);

    Class c1 = objc_getClass("ViewController");
    Class c2 = [swizzle_ViewController class];
    Method m1 = class_getInstanceMethod(c1, @selector(originalMethodName));
    Method m2 = class_getInstanceMethod(c2, @selector(swizzle_originalMethodName));
    method_exchangeImplementations(m1, m2);
}

@end

スウィズルは問題なく動作しています (以下の出力を参照) originalMethodNameswizzle_originalMethodName

2016-08-17 14:18:51.765 testMacOS[7295:1297055] FAKE initializer
2016-08-17 14:18:51.822 testMacOS[7295:1297055] REAL -[ViewController viewDidLoad]
2016-08-17 14:18:51.822 testMacOS[7295:1297055] FAKE -[swizzle_ViewController swizzle_originalMethodName]

私は使用しようとしましNSInvocationたが、運がありません。私が間違っていることはありますか?

Class c1 = objc_getClass("ViewController");
Method m1 = class_getInstanceMethod(c1, @selector(originalMethodName));
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:method_getTypeEncoding(    m1)];
NSInvocation *originalInvocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[originalInvocation invoke];
4

2 に答える 2

11

クラス階層内でスウィズルしている場合、たとえば、その祖先メソッドの 1 つを独自のメソッドの 1 つとスウィズルするサブクラスがある場合、スウィズルインされたメソッドが自分自身を呼び出しているように見えるだけです。その呼び出しは、実際にはスウィズルアウトされたメソッドを呼び出します。メソッドが交換されているためです。あなたの場合、次のようになります。

- (void)swizzle_originalMethodName
{
   NSLog(@"FAKE %s", __func__);
   [self swizzle_originalMethodName]; // call original
}

これは、クロスクラスのスウィズリングであるため、このケースでは機能しないためself、スウィズルアウト メソッドでクラスを参照しません。そして、swizzled-out メソッドを呼び出すことができる、swizzling クラスのインスタンスがありません...

これを修正する簡単な方法の 1 つを次に示します。スウィズルイン メソッドで実行できるようにする必要があるのは、元の実装を呼び出すことです。スウィズルをセットアップするときにそれを取得できます。

Objective-C では、メソッドは、最初の 2 つの引数がメソッドが呼び出されるオブジェクト参照であり、セレクターと残りの引数がメソッドの引数である関数によって実装されます。たとえば、NSStringメソッド:

- (NSRange)rangeOfString:(NSString *)aString

次のような関数によって実装されます。

NSRange rangeOfStringImp(NSString *self, SEL cmd, NSString *aString)

を使用して、この実装関数への関数ポインタを取得できますmethod_getImplementation

コードに対して、最初に、swizzle_ViewControllerスウィズルしているメソッドの実装関数の型を宣言し、関数ポインターを格納するグローバルを宣言します。

typedef void (*OriginalImpType)(id self, SEL selector);
static OriginalImpType originalImp;

initializerメソッドでメソッドの実装を保存する必要があります。次の行を追加することでこれを行うことができます。

Method m1 = class_getInstanceMethod(c1, @selector(originalMethodName));
originalImp = (OriginalImpType)method_getImplementation(m1); // save the IMP of originalMethodName

最後に、スウィズルイン メソッドで保存済みの実装を呼び出します。

- (void)swizzle_originalMethodName
{
   NSLog(@"FAKE %s", __func__);
   originalImp(self, @selector(originalMethodName)); // call the original IMP with the correct self & selector
}

オプション: 上記は正しく機能しますが、必要以上のことを行います。メソッドの実装は両方とも交換され、一方はグローバル変数に格納されます。実際に必要なのは、元の実装を保存してからm1、その実装を に設定することだけです。のそれm2。への呼び出しを次のように置き換えることで、これに対処できますmethod_exchangeImplementations

method_setImplementation(m1, method_getImplementation(m2));

もう少しタイピングが増えますが、実際に何をする必要があるかについてはいくらか明確です。

HTH

于 2016-08-18T00:48:21.480 に答える