一、前言
在维护一个项目时碰见一个问题,调用了一个方法后不起作用,这种问题一般有两个解决思路,一个是看方法有没有起作用,另一个是看是不是其他的方法打断了这个方法,比如设置 UIButton 的隐藏,但在另一个地方,又把它设置成显示了。这种问题在工程里通过调试代码一般都很好解决。
但是当它发生在我调用了一个 SDK 方法不起作用时,按照上文的思路,一查看是否是 SDK 方法的问题,这个除了向 SDK 提供人员反应没有其他更好的解决方法了,二是查看是否其他地方也调用了这个方法,使用了不同的参数导致本次的调用失效了。如果这个方法调用的少,可以全局搜索,然后注释掉方法,但是如果之前项目不是你开发的,你只是后期切入进来,同时代码结构一时半会还不能很好把握到,总之想快速利落的排除问题,这时你可以考虑用 Runtime 的特性,把 SDK 里的方法用自己写的替换掉。
先老调重弹一遍,什么是 Runtime?
二、概念
Objective-C是动态语言,它将很多静态语言在编译和链接时做的事放到了运行时,这个运行时系统就是 Runtime。
Runtime 其实就是一个库,它基本上是用 C 和汇编写的一套 API,这个库使C语言有了面向对象的能力。
静态语言:在编译的时候会决定调用哪个函数。
动态语言(OC):在运行的时候根据函数的名称找到对应的函数来调用。
然后还有一些需要了解的基本概念:
- isa:OC 中,类和类的实例在本质上没有区别,都是对象,任何对象都有isa 指针,它指向类或元类。
- SEL:SEL(又叫选择器)是方法的 selector 的指针。方法的 selector 表示运行时方法的名字。OC在编译时,会依据每一个方法的名字、参数,生成一个唯一的整型标识(Int类型的地址),这个标识就是 SEL。
- IMP:IMP 是一个函数指针,指向方法最终实现的首地址。SEL 就是为了查找方法的最终实现IMP。
- Method:用于表示类定义中的方法,它的结构体中包含一个 SEL 和 IMP,相当于在 SEL 和 IMP 之间作了一个映射。
- 消息机制:任何方法的调用本质就是发送一个消息。编译器会将消息表达式[receiver message]转化为一个消息函数 objc_msgSend(receiver, selector)。
- Runtime 的使用:获取属性列表,获取成员变量列表,获得方法列表,获取协议列表,方法交换(黑魔法),动态的添加方法,调用私有方法,为分类添加属性。
三、应用场景
- 关联对象(Objective-C Associated Objects)给分类增加属性
- 方法交换 / 黑魔法(Method Swizzling)
- 实现 NSCoding 的自动归档和自动解档
- 实现字典和模型的自动转换(MJExtension)
这里介绍前两种。
1、关联对象(Objective-C Associated Objects)给分类增加属性
我们都是知道分类是不能自定义属性和变量的。下面通过关联对象实现给分类添加属性。
关联对象 Runtime提 供了下面几个接口:
1 | // 关联对象 |
例子:
1 | ///----------------------- |
2、方法交换(黑魔法)
下面就是 runtime 的重头戏了,被称作黑魔法的方法交换 Swizzling。交换方法是在 method_exchangeImplementations 里发生的。
使用 Swizzling 的过程中要注意两个问题:
Swizzling 要在 +load 方法中执行
运行时会自动调用每个类的两个方法,+load 与 +initialize。
+load 会在 main 函数之前调用,并且一定会调用。
+initialize 是在第一次调用类方法或实例方法之前被调用,有可能一直不被调用。
一般使用 Swizzling 是为了影响全局,所以为了方法交换一定成功,Swizzling 要放在 +load 中执行。
Swizzling 要在 dispatch_once 中执行
Swzzling 是为了影响全局,所以只让它执行一次就可以了,所以要放在 dispatch_once 中。
例子:
1 | ///----------------------- |