iOS开发 - method swizzle方式的选择

1 method swizzle方式的选择

1.1 错误的swizzle方式

根据 right-way-to-swizzle 文章的阐述,当我们进行方法交换时,实质是交换了objc_method结构体中的IMP函数指针

struct objc_method
     SEL method_name         OBJC2_UNAVAILABLE;
     char *method_types      OBJC2_UNAVAILABLE;
     IMP method_imp          OBJC2_UNAVAILABLE;
}

例如:我们hook一个方法method,这里叫 originalMethodName,通过method_exchangeImplementations交换其实现IMP

未swizzle前,method的内容如下

 Method m1 { //this is the original method. we want to switch this one with
             //our replacement method
      SEL method_name = @selector(originalMethodName)
      char *method_types = “v@://returns void, params id(self),selector(_cmd)
      IMP method_imp = 0x000FFFF (MyBundle`[MyClass originalMethodName])
 }
 
 Method m2 { //this is the swizzle method. We want this method executed when [MyClass
             //originalMethodName] is called
       SEL method_name = @selector(swizzle_originalMethodName)
       char *method_types = “v@:”
       IMP method_imp = 0x1234AABA (MyBundle`[MyClass swizzle_originalMethodName])
 }

进行method_exchangeImplementations交换objc_method中的IMP

m1 = class_getInstanceMethod([MyClass class], @selector(originalMethodName));
m2 = class_getInstanceMethod([MyClass class], @selector(swizzle_originalMethodName));
method_exchangeImplementations(m1, m2)

swizzle method之后,method的内容如下:

 Method m1 { //this is the original Method struct. we want to switch this one with
             //our replacement method
     SEL method_name = @selector(originalMethodName)
     char *method_types = “v@://returns void, params id(self),selector(_cmd)
     IMP method_imp = 0x1234AABA (MyBundle`[MyClass swizzle_originalMethodName])
 }
 
 Method m2 { //this is the swizzle Method struct. We want this method executed when [MyClass
            //originalMethodName] is called
     SEL method_name = @selector(swizzle_originalMethodName)
     char *method_types = “v@:”
     IMP method_imp = 0x000FFFF (MyBundle`[MyClass originalMethodName])
 }

可以看出SELIMP并不匹配,.m文件我们一般会这样写

.m我们一般会这样写

- (void)originalMethodName {
	// do your logic
}

- (void)swizzle_originalMethodName {
	// hook before action
	[self swizzle_originalMethodName]; //调用原有的originalMethodName方法
	// hook after action
}

即调用swizzle_originalMethodName来触发原方法的原因是SELIMP并不对应,当方法实现中,使用_cmd来获取方法名时,就获取的SEL和预计的不一致,为了解决这样的问题,就在方法交换时,不破坏objc_method结构的含义,SEL和IMP对应。

_cmd在Objective-C的方法中表示当前方法(objc_method)的selector

思考:那么使用__FUNCTION__获取函数名是否就可以了呢?

对viewDidLoad方法swizzle后同一方法中两个输出打印为:

__FUNCTION__: -[CCRootViewController viewDidLoad_swizzle] SEL:viewDidLoad

__FUNCTION__是可以判断函数名,而不是取SEL

- (void) originalMethodName //m1
 {
          assert([NSStringFromSelector(_cmd) isEqualToString:@“originalMethodNamed”]); //方法交换后,这里断言就会拦截掉,不会往下走了,因为_cmd并不是originalMethodNamed,而是swizzle_originalMethodName
          //method_exchangedImplementations()
          //…
 

1.2 正确的swizzle方式

避免使用Objective-C声明方法的方式,因为其会创建一个objc_method结构体,我们仅仅需要替换其IMP,那就创建一个C函数(用于替换IMP)

void __Swizzle_OriginalMethodName(id self, SEL _cmd)
 {
      //code
 }
 
 IMP swizzleImp = (IMP)__Swizzle_OriginalMethodName;

使用method_setImplementation(method, swizzleImp);方法来设定其objc_method中的IMP,其返回值会返回原始的IMP

IMP originalImp = method_setImplementation(method,swizzleImp);

原始的IMP我们可以通过函数指针来存储,这样在swizzleImp实现中,我们调用这个函数指针,既可以达到了hook的目的。


1.3 案例

原文中的用例贴出:

SwizzleExampleClass.h

@interface SwizzleExampleClass : NSObject
 - (void) swizzleExample;
 - (int) originalMethod;
 @end

SwizzleExampleClass.m

 static IMP __original_Method_Imp;
 int _replacement_Method(id self, SEL _cmd)
 {
      assert([NSStringFromSelector(_cmd) isEqualToString:@"originalMethod"]);
      //code
     int returnValue = ((int(*)(id,SEL))__original_Method_Imp)(self, _cmd);
    return returnValue + 1;
 }
 
 @implementation SwizzleExampleClass
 
 - (void) swizzleExample //call me to swizzle
 {
     Method m = class_getInstanceMethod([self class],
 @selector(originalMethod));
     __original_Method_Imp = method_setImplementation(m,
 (IMP)_replacement_Method);
 }
 
 - (int) originalMethod
 {
        //code
        assert([NSStringFromSelector(_cmd) isEqualToString:@"originalMethod"]);
        return 1;
 }
@end
©️2020 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值