【问题标题】:Calling original function from swizzled function从 swizzled 函数调用原始函数
【发布时间】:2016-08-17 18:28:30
【问题描述】:

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

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

@implementation ViewController

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

- (void)viewDidLoad {
    [super viewDidLoad];

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

    [self originalMethodName];
}

@end

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

调酒器工作得很好(如下面的输出所示),但现在我希望能够从swizzle_originalMethodName 调用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];

【问题讨论】:

  • 直接拨打[self swizzle_originalMethodName];怎么样?该选择器应在交换后指向原始实现。
  • 这是天才。不知道为什么我没有想到。我仍然收到一个错误,我不知道如何解决它。 -[ViewController swizzle_originalMethodName]: unrecognized selector sent to instance 0x7ff72974a670
  • 啊,对……你在不同的班级里混日子。我需要考虑一下。
  • 这个问题在 SO 上出现过几次,但从未得到完全回答。一些答案对 swizzling 进行了令人难以置信的深度,但从未包含任何经过测试并证明可以工作的代码。这让我疯狂了好几个小时。哈哈

标签: objective-c swizzling method-swizzling


【解决方案1】:

如果您在类层次结构中混杂,例如您有一个子类,它将其祖先方法之一与它自己的一种方法进行混合,然后您只需让 swizzled 方法 显然 调用自身 - 该调用实际上将调用 swizzled 方法作为方法已被交换。在您的情况下,您将拥有:

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

这在您的情况下不起作用,因为您是跨类混合,因此self 不会使用混合方法引用该类。而且你没有 swizzling 类的实例,你可以调用 swizzled-out 方法...

这是解决此问题的一种简单方法,您的 swizzle-in 方法需要能够做的是调用原始实现,您可以在设置 swizzling 时得到它。

在 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

最后让你的 swizzled 方法调用保存的实现:

- (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

【讨论】:

  • 你太棒了!这行得通!昨天我在墙上撞了几个小时,试图让这个工作正常进行。谢谢
【解决方案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")

【讨论】:

    猜你喜欢
    • 2023-03-15
    • 2014-05-07
    • 2016-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-03
    • 1970-01-01
    • 2012-05-12
    相关资源
    最近更新 更多