从混合功能调用原始功能

时间:2016-08-17 18:28:30

标签: objective-c swizzling method-swizzling

我正在搞乱方法调配,并希望在执行method_exchangeImplementations后调用原始函数。我为此设置了两个项目。

第一个项目是应用程序的主要项目。该项目包括应用程序的所有逻辑。请注意,视图加载时会调用originalMethodName

@implementation ViewController

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

- (void)viewDidLoad {
    [super viewDidLoad];

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

    [self originalMethodName];
}

@end

第二个项目仅包含调配代码。我有一个方法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

混合工作正常(如下面的输出所示),但现在我希望能够从originalMethodName

致电swizzle_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];

2 个答案:

答案 0 :(得分:3)

如果您在类层次结构中徘徊,例如你有一个子类,它的一个祖先方法用它自己的一个,然后你只是有一个混合方法显然调用自己 - 该调用实际上会调用swizzled-out方法作为方法已被交换。在你的情况下,你会有:

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

这不适用于您的情况,因为您是跨类调配,因此self不会使用调出方法引用该类。而且你没有一个可以称之为调高方法的混合类的实例......

以下是解决此问题的一种简单方法,您的调酒方法需要做的是调用原始实现,并且您可以在设置调配时获得该功能。

在Objective-C中,方法由一个函数实现,该函数的前两个参数是调用该方法的对象引用,而选择器和其余参数是该方法的参数。例如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
}

可选:以上工作正常,但它比需要的更多 - 方法实现都是交换的,一个存储在全局变量中,你真正需要做的就是保存{{1的原始实现然后将其实现设置为m1的实现。您可以将m2的调用替换为:

来解决此问题
method_exchangeImplementations

这是一个更多的打字,但实际上需要做的更清楚。

HTH

答案 1 :(得分:2)

有一个稍微简单的选项来调用原始实现,不需要您直接存储方法实现。交换方法的实现时,原始实现将存储在swizzler类中。您可以使用class_getMethodImplementation函数来获取复杂的实现。这是一个操场样本:

import Cocoa

let fooSelector = Selector("fooWithArg:")
let swizzledFooSelector = Selector("swizzled_fooWithArg:")

class A: NSObject {
    @objc dynamic func foo(arg: String) {
        print("Foo \(arg) in A")
    }
}

class B: NSObject {
    private typealias FooFunc = @convention(c) (AnyObject, Selector, String) -> Void
    @objc func swizzled_foo(arg: String) {
        print("Swizzled_foo \(arg) in B")

        unsafeBitCast(
            class_getMethodImplementation(B.self, swizzledFooSelector),
            to: FooFunc.self
        )(self, fooSelector, arg)
    }
}

method_exchangeImplementations(
    class_getInstanceMethod(A.self, fooSelector)!,
    class_getInstanceMethod(B.self, swizzledFooSelector)!
)

A().foo(arg: "bar")

Calling the swizzled out implementation