iOS Method Swizzle黑魔法小记

本文主要内容为用runtime实现Swizzle,即调换两个方法的实现

一点iOS runtime的基本知识

Objective-C是一门动态语言,每个方法在运行时会被动态转为消息发送,方法的实现由运行时决定

[foo message:@"ss"] 向foo对象发送message的消息会被runtime转换成

((void*)(id,SEL,id))(objc_msgSend)(foo,@selector(message),@"ss")

当然在代码里上面这两个方法都是等价的,实现效果都一样

objc_msgSends是一个类型为((void*)(id,SEL,id))的函数指针。

下面说说objc_msgSends的机制


看看Objective-C中对象的定义

对象定义

类定义

如上面[foo message:@"ss"] 中的foo对象是objc_object结构类型的,他里面有一个isa指针指向他的类对象Class,所以foo对象的所有信息都可以在Class里面找到,如实例对象,方法列表等

当执行[foo message:@"ss"]的时候

  1. [foo message:@"ss"]转换为
((void*)(id,SEL,id))(objc_msgSend)(foo,@selector(message),@"ss")
  1. 以上述[foo message:@"ss"]为例子,runtime会根据foo对象的isa指针找到该对象所属的类,然后以@selector(message)为键在该对象的方法列表(methodLists)寻找方法运行,若最终找不到方法,runtime提供3次机会将方法进行处理,否则最后会抛出unrecognized selector sent to XXX的异常
  2. 三次补救机会看Objective-C Runtime 1小时入门教程
本文的Swizzle黑魔法从上面第二点入手,替换methodLists中的方法

本文例子,替换UIViewcontroller的viewdidLoad方法
新建SwizzleHelper类

屏幕快照 2016-09-30 下午5.46.29.png
  1. 首先用class_getInstanceMethod获取UIViewController的ViewdidLoad方法和SwizzleHelper的
    jm_viewDidLoad方法
    Mehod是一个结构体,如下
Method结构
  • SEL method_name是方法的名字
  • IMP mehod_imp是一个函数指针,对应方法的实现(可以理解为方法{}里面的内容),下面将会替换这个IMP来实现方法替换
  1. class_addMethod向UIViewController类中添加jm_viewDidLoad方法
  2. 再获取添加后UIViewController中的jm_viewDidLoad方法
  3. method_exchangeImplementations将UIViewController中的ViewdidLoad方法和jm_viewDidLoad的实现进行替换
屏幕快照 2016-09-30 下午7.35.20.png

运行后打印,证明方法已被替换

屏幕快照 2016-09-30 下午6.07.45.png

再说说下面这个替换的方法


屏幕快照 2016-09-30 下午6.14.04.png

为什么再调用一次呢,因为jm_viewDidLoad方法的实现已被替换,jm_viewDidLoad对应的是原viewdidLoad方法的实现。

替换后调用过程

当UIViewController对象调用viewDidLoad的时候,会转成((void*)(id,SEL,id))(objc_msgSend)((UIViewcontroller对象),@selector(viewDidLoad)),此时runtime会根据UIViewcontroller对象的isa指针找到该对象所属的类,然后以@selector(viewDidLoad)为键在该对象的方法列表 (methodLists)寻找方法运行,此时viewDidLoad方法的实现已经替换为jm_viewDidLoad的实现方法,从而实现了方法替换.

思考1

屏幕快照 2016-09-30 下午7.16.44.png

尝试不将jm_viewDidLoad方法加入UIViewcontroller类里面,然后直接替换jm_viewDidLoad和viewdidLoad方法的实现,却出现下面异常。

屏幕快照 2016-09-30 下午7.21.32.png

为什么呢?

因为虽然实现替换成功了,但由于jm_viewDidLoad里面又调用了一次[self jm_viewDidLoad],又由于UIViewcontroller中没有添加jm_viewDidLoad方法,所以调用[self jm_viewDidLoad]时会找不到方法从而抛出异常。

思考2

为什么在+(void)load方法中进行替换呢?

+(void)load方法在main()前类载入内存时会调用一次。详细看
NSObject的load和initialize方法
你真的了解 Objective-C 中的load 方法么?

Demo下载

Demo下载地址

参考

iOS class_addMethod使用
说说objcRuntime的一些妙用(class_addMethod,class_replaceMethod)
iOS开发:Runtime黑魔法,替换系统类方法及属性
Objective-C Runtime 1小时入门教程

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

相关阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 2,076评论 0 9
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 136,654评论 19 139
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,628评论 30 472
  • 望山山依旧 草木已泛黄 寒风穿心过 方知冬来时
    野孩子1982阅读 380评论 0 0
  • 其实每天晚上就只是梦到你的名字我也会第二天很开心。
    海底琼楼阅读 237评论 0 1

友情链接更多精彩内容