runtime中的交换方法method_exchangeImplementations存在的问题

  Method method1 = class_getInstanceMethod(self, @selector(eat));
  Method method2 = class_getInstanceMethod(self, @selector(anotherEat));
  method_exchangeImplementations(method1, method2);

先看下上面这段简单的方法替换代码。

方法替换到底做了什么?

上面的代码首先是通过方法名获取了两个方法,然后将两个方法的实现替换了。

方法的底层结构

  struct method_t {
SEL method_name 
IMP method_imp  
char * types  
}  

我们知道在类对象和元类对象中都有一个方法列表,分别存储着实例方法和类方法,存储着的方法底层结构如上面代码所示,它是一个结构体,在结构体中有三个参数,他们的含义分别是:

method_name:方法的名字
method_imp:保存着一个指针,指向了函数的具体实现。实际方法替换真正替换的是method_imp即方法的具体实现。
types:能代表这个方法的字符,用法可以忽略。

直接使用method_exchangeImplementations方法的问题

我们再看看直接使用method_exchangeImplementations方法进行方法的替换有什么问题呢?
假设有一个Father类,有一个继承自Father类的Son类,Father类中有一个eat方法,Son类中没有重写eat方法,现在在Son的一个分类中对Son的eat方法进行替换。
看代码:

  + (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    
    Method method1 = class_getInstanceMethod(self, @selector(eat));
    Method method2 = class_getInstanceMethod(self, @selector(anotherEat));
    method_exchangeImplementations(method1, method2);
    
});
}

- (void)anotherEat{
  NSLog(@"self:%@", self);
  [self anotherEat];
  NSLog(@"替换之后的吃的方法...");
}

创建Father、Son的实例并调用实例方法

Son * s = [Son new];
[s eat];

Father * f = [Father new];
[f eat];

打印:

 runtime的两个方法替换[18967:1520161] self:<Son: 0x281749590>
 runtime的两个方法替换[18967:1520161] 爸爸吃东西...
 runtime的两个方法替换[18967:1520161] 替换之后的吃的方法...
 runtime的两个方法替换[18967:1520161] self:<Father: 0x2817491e0>
 runtime的两个方法替换[18967:1520161] -[Father anotherEat]: unrecognized selector sent to instance 0x2817491e0

程序发生了崩溃,报的错是在Father类中找不到anotherEat方法。Son实例调用eat方法是完全没有问题的。我们先分析一下Son。

分析Son

当在Son的分类中替换了Son的eat和anotherEat两个方法的实现后,Son实例调用eat实际调用的是anotherEat方法的具体实现,调用anotherEat实际调用的是eat方法的具体实现。anotherEat方法名在Son的类对象列表中能找到,所以是没问题的。
再来分析Father

分析Father

Father类中的eat方法的具体实现(imp)被替换成了anotherEat方法的具体实现,当调用Father实例的eat方法时就会走anotherEat方法的具体实现,如果anotherEat方法中不调用anotherEat方法也是没有问题的,但如果调用了anotherEat(如放数组越界中还是要调用系统的方法的),由于Father类对象中的方法列表中并没有名字为anotherEat的这个方法,所以报了找不到这个方法的错误。
盗用别人的一个图:


image.png

简单一句话:

替换了父类某个方法的具体实现,但并没有改变该方法的方法名,从而可能导致崩溃。

那么我们要想替换两个方法的实现同时避免出现以上问题该怎么办呢?这个时候就需要我们利用runtime的动态解析即动态添加方法,请看runtime中的交换方法class_replaceMethod
这篇文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容