从 swizzled 函数调用原始函数
Calling original function from swizzled function
我正在处理方法调配,想在执行 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 工作正常(如下面的输出所示),但现在我希望能够从 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];
如果您在 class 层次结构中调配,例如你有一个 subclass 将它的一个祖先方法与它自己的一个方法混合,然后你只需让 swizzled-in 方法 apparently 调用它自己——那个调用实际上会调用swizzled-out 方法,因为方法已被交换。在您的情况下,您将拥有:
- (void)swizzle_originalMethodName
{
NSLog(@"FAKE %s", __func__);
[self swizzle_originalMethodName]; // call original
}
这在您的情况下不起作用,因为您正在交叉 class 调配,因此 self
不会使用调配方法引用 class。而且你没有 swizzling 的实例 class 你可以调用 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
最后让您的调配方法调用保存的实现:
- (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
调用原始实现有一个更简单的选项,它不需要您直接存储方法实现。当您交换方法的实现时,原始实现将存储在 swizzler class 中。您可以使用 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")
我正在处理方法调配,想在执行 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 工作正常(如下面的输出所示),但现在我希望能够从 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];
如果您在 class 层次结构中调配,例如你有一个 subclass 将它的一个祖先方法与它自己的一个方法混合,然后你只需让 swizzled-in 方法 apparently 调用它自己——那个调用实际上会调用swizzled-out 方法,因为方法已被交换。在您的情况下,您将拥有:
- (void)swizzle_originalMethodName
{
NSLog(@"FAKE %s", __func__);
[self swizzle_originalMethodName]; // call original
}
这在您的情况下不起作用,因为您正在交叉 class 调配,因此 self
不会使用调配方法引用 class。而且你没有 swizzling 的实例 class 你可以调用 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
最后让您的调配方法调用保存的实现:
- (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
调用原始实现有一个更简单的选项,它不需要您直接存储方法实现。当您交换方法的实现时,原始实现将存储在 swizzler class 中。您可以使用 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")