详解iOS Method Swizzling使用陷阱

 更新时间:2020年11月6日 08:11  点击:1972

在阅读团队一项目源码时,发现Method Swizzling的写法有些瑕疵。这篇文章主要就介绍iOS Method Swizzling的正确写法应该是什么样的。

下面是iOS Method Swizzling的一种实现:

+ (void)load {
  Class class = [self class];

  SEL fromSelector = @selector(func);
  SEL toSelector = @selector(easeapi_func);

  Method fromMethod = class_getInstanceMethod(class, fromSelector);
  Method toMethod = class_getInstanceMethod(class, toSelector);

  method_exchangeImplementations(fromMethod, toMethod);
}

这种写法在一些时候能正常工作,但实际上有些问题。那么问题在哪里呢?

一个例子

为了说明这个问题,我们先来假设一个场景:

@interface Father: NSObject
-(void)easeapi;
@end
@implementation Father
-(void)easeapi {
  //your code
}
@end

//Son1继承自Father
@interface Son1: Father
@end
@implementation Son1
@end

//Son2继承自Father,并HOOK了easeapi方法。
@interface Son2: Father
@end
@implementation Son2
+ (void)load {
  Class class = [self class];

  SEL fromSelector = @selector(easeapi);
  SEL toSelector = @selector(new_easeapi);

  Method fromMethod = class_getInstanceMethod(class, fromSelector);
  Method toMethod = class_getInstanceMethod(class, toSelector);

  method_exchangeImplementations(fromMethod, toMethod);
}
-(void)new_easeapi {
  [self new_easeapi];
  //your code
}
@end

看样子没什么问题,Son2的方法也交换成功,但当我们执行[Son1 easeapi]时,发现CRASH了。

'-[Son1 new_easeapi]: unrecognized selector sent to instance 0x600002d701f0''

这就奇怪了,我们HOOK的是Son2的方法,怎么会产生Son1的崩溃?

为什么会发生崩溃

要解释这个问题,还是要回到原理上。

首先明确一点,class_getInstanceMethod会查找父类的实现。

在上例中,easeapi是在Son2的父类Father中实现的,执行method_exchangeImplementations之后,Father的easeapi和Son2的new_easeapi进行了方法交换。

交换之后,当Son1(Father的子类)执行easeapi方法时,会通过「消息查找」找到Father的easeapi方法实现。

重点来了!

由于已经发生了方法交换,实际上执行的是Son2的new_easeapi方法。

-(void)new_easeapi {
  [self new_easeapi];
  //your code
}

可恶的是,在new_easeapi中执行了[self new_easeapi]。此时这里的self是Son1实例,但Son1及其父类Father中并没有new_easeapi的SEL,找不到对应的SEL,自然就会CRASH。

什么情况下不会有问题?

上面说了:「这种写法在一些时候能正常工作」。那么,到底什么时候直接执行method_exchangeImplementations不会有问题呢?

至少在下面几种场景中都不会有问题:

Son2中有easeapi的实现

在上例中,如果我们在Son2中重写了easeapi方法,执行class_getInstanceMethod(class, fromSelector)获取到的是Son2的easeapi实现,而不是Father的。这样,执行method_exchangeImplementations后,不会影响到Father的实现。

new_easeapi实现改进

- (void) new_easeapi {
  //[self new_easeapi];//屏蔽掉这句代码
  //your code
}

在这个场景中,由于不会执行[self new_easeapi],也不会有问题。但这样就达不到HOOK的效果。

改进优化

推荐的Method Swizzling实现:

+ (void)load {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    Class class = [self class];

    SEL fromSelector = @selector(easeapi);
    SEL toSelector = @selector(new_easeapi);

    Method fromMethod = class_getInstanceMethod(class, fromSelector);
    Method toMethod = class_getInstanceMethod(class, toSelector);

    if(class_addMethod(class, fromSelector, method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {
      class_replaceMethod(class, toSelector, method_getImplementation(fromMethod), method_getTypeEncoding(fromMethod));
    } else {
      method_exchangeImplementations(fromMethod, toMethod);
    }
  });
}

可以看到,至少有两点变化:

dispatch_once
尽管dyld能够保证调用Class的load时是线程安全的,但还是推荐使用dispatch_once做保护,防止极端情况下load被显示强制调用时,重复交换(第一次交换成功,下次又换回来了...),造成逻辑混乱。

增加了class_addMethod判断
class_addMethod & class_replaceMethod
还是从定义上理解。

class_addMethod
给指定Class添加一个SEL的实现(或者说是SEL和指定IMP的绑定),添加成功返回YES,SEL已经存在或添加失败返回NO。

它有两个需要注意的点:

  • 如果该SEL在父类中有实现,则会添加一个覆盖父类的方法;
  • 如果该Class中已经有SEL,则返回NO。

执行class_addMethod能避免干扰到父类,这也是为什么推荐大家尽量先使用class_addMethod的原因。显然易见,因为iOS Runtime消息传递机制的影响,只执行method_exchangeImplementations操作时可能会影响到父类的方法。基于这个原理,如果HOOK的就是本类中实现的方法,那么直接用method_exchangeImplementations也是完全没问题的。

class_replaceMethod

  • 如果该Class不存在指定SEL,则class_replaceMethod的作用就和class_addMethod一样;
  • 如果该Class存在指定的SEL,则class_replaceMethod的作用就和method_setImplementation一样。

到此这篇关于详解iOS Method Swizzling使用陷阱的文章就介绍到这了,更多相关iOS Method Swizzling内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

[!--infotagslink--]

相关文章

  • MySQL性能监控软件Nagios的安装及配置教程

    这篇文章主要介绍了MySQL性能监控软件Nagios的安装及配置教程,这里以CentOS操作系统为环境进行演示,需要的朋友可以参考下...2015-12-14
  • iOS APP h5快捷程序 .mobileconfig的生成

    1.从APP Store 下载Apple Configurator 2从一个管理点管理所有iOS设备应用程序,文档和配置文件。想要确保您的所有家庭成员在其每台iOS设备上都有类似的应用和文档,管理日益增...2021-12-23
  • iOS设置UIButton文字显示位置和字体大小、颜色的方法

    这篇文章给大家分享了iOS如何设置UIButton的文字显示位置和字体的大小、颜色,文中给出了示例代码,相信对大家的学习和理解很有帮助,有需要的朋友们下面来一起看看吧。...2020-06-30
  • iOS如何将图片裁剪成圆形

    这篇文章主要为大家详细介绍了iOS如何将图片裁剪成圆形,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-06-30
  • iOS给border设置渐变色的方法实例

    这篇文章主要给大家介绍了关于iOS给border设置渐变色的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-09
  • iOS新版微信底部返回横条问题的解决

    这篇文章主要介绍了iOS新版微信底部返回横条问题的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-30
  • vue+axios全局添加请求头和参数操作

    这篇文章主要介绍了vue+axios全局添加请求头和参数操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-24
  • iOS蓝牙设备名称缓存问题的解决方法

    这篇文章主要给大家介绍了关于iOS蓝牙设备名称缓存问题的解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-12-08
  • iOS新版微信底部工具栏遮挡问题完美解决

    这篇文章主要介绍了iOS新版微信底部工具栏遮挡问题完美解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-30
  • iOS新增绘制圆的方法实例代码

    这篇文章主要给大家介绍了关于iOS新增绘制圆的方法,文中通过示例代码介绍的非常详细,对各位iOS开发者们具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧...2020-06-30
  • iOS UIBezierPath实现饼状图

    这篇文章主要为大家详细介绍了iOS UIBezierPath实现饼状图,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-03-20
  • 封装 axios+promise通用请求函数操作

    这篇文章主要介绍了封装 axios+promise通用请求函数操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-12
  • IOS获取各种文件目录路径的方法

    ios获取文件路径的方法,iphone沙箱模型的四个文件夹,通过documents,tmp,app,Library得到模拟器路径的简单方式,下面小编整理相关资料,把IOS获取各种文件目录路径的方式总结如下,需要的朋友可以参考下...2020-06-30
  • iOS UICollectionView实现卡片效果

    这篇文章主要为大家详细介绍了iOS UICollectionView实现卡片效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-06-30
  • Vue使用axios引起的后台session不同操作

    这篇文章主要介绍了Vue使用axios引起的后台session不同操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-14
  • iOS实现电子签名

    这篇文章主要为大家详细介绍了iOS实现电子签名,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-12-08
  • iOS实现循环滚动公告栏

    这篇文章主要为大家详细介绍了iOS实现循环滚动公告栏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-03-20
  • iOS APP实现微信H5支付示例总结

    这篇文章主要介绍了iOS APP实现微信H5支付示例总结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-30
  • jQuery调用WebMethod(PageMethod) NET2.0的方法

    这篇文章主要介绍了jQuery调用WebMethod(PageMethod) NET2.0的方法,结合实例形式分析了jQuery基于ajax调用.NET方法的相关技巧,需要的朋友可以参考下...2016-04-18
  • iOS WKWebview 白屏检测实现的示例

    这篇文章主要介绍了iOS WKWebview 白屏检测实现的示例,帮助大家更好的进行ios开发,感兴趣的朋友可以了解下...2020-10-20