runtime在OC中的应用非常广泛,可以动态创建和获取类、成员变量、属性、方法。
一、类
常用API:
1 | // 动态创建一个类(参数:父类,类名,额外的内存空间),创建完成后一定要注册,否则无法使用 |
注册类一定要放在添加成员变量后,因为类注册后,类的结构就已经固定了。添加方法、属性、协议不受此限制,具体可参考前面章节介绍:类的结构体。成员变量放在class_ro_t
里面,方法、属性、协议放在class_rw_t
里面。
二、成员变量
常用API:
1 | // 获取一个实例变量信息 |
三、属性
常用API:
1 | // 获取一个属性 |
四、方法
常用API:
1 | // 获得一个实例方法、类方法 |
五、应用
runtime的实际应用场景有很多,我们把一些常用的API做一些简单的示例,具体的细节需要根据业务进行调整和变换。
5.1. 动态创建类
1 | void run(id self, SEL _cmd) { |
5.2. 私有属性赋值/取值
修改UITextField的placeholder
颜色和字体,可以通过attributedPlaceholder
添加富文本达到目的。也可以通过获取UITextField的成员变量,查看控制placeholder
的是哪一个成员变量,然后使用KVC方式修改成员变量的值。
1 | // 获取UITextField的成员变量(目的:查看是哪个属性控制的placeholder) |
在实际开发中,不建议直接修改系统控件的成员变量,因为不确定在未来什么时候成员变量不存在或被苹果禁止(也可能会影响APP上架)。所以一般情况下,我们都是自定义一个UI控件或组件。
5.3. 字典转模型
字典转模型我们一般使用的是第三方库(例如:YYModel,MJExtension等)。但里面的核心就是使用的runtime一些API,更多的是处理细节问题。
1 | @implementation NSObject (Json) |
以上代码仅仅是一个示范,真正的字典转模型需要处理很多细节问题(比如:继承、自定义映射关系、模型嵌套等)。
5.4. 拦截按钮点击事件
UIButton继承自UIControl,所有点击事件触发时都会调用UIControl的方法- (void)sendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event
,因此如果要拦截按钮的点击事件,只需要把这个系统方法和自定义的方法交换实现即可。
1 | @implementation UIControl (HookEvent) |
方法交换的本质是把class_rw_t -> methods -> method_list_t -> method_t
中的IMP交换了,并且使用method_exchangeImplementations
后,缓存的方法会清空。所以需要调用交换后的方法才可以调用交换前的方法实现。
一般情况下,我们把方法交换称为hook(钩子)。
5.5. 防止数组添加空值导致崩溃
使用NSMutableArray添加成员时,addObject:
不能添加nil,而addObject:
的本质是调用insertObject:atIndex:
,所以我们只需要hookinsertObject:atIndex
方法就可以。
NSMutableArray的真实类型并不是NSMutableArray。NSString、NSArray、NSDictionary等都是类簇(真实类型和暴露的类型不一致),真实类型是其他类型。所以在传入cls时一定要注意,否则方法会交换失败。
1 | @implementation NSMutableArray (Extension) |
5.6. 防止字典的key是空值导致崩溃
NSDictionary添加键值对时真实类型是__NSDictionaryM
,根据键取值时字典的真实类型有很多种,但都继承自__NSDictionaryI
。
1 | @implementation NSMutableDictionary (Extension) |