Day6-Runtime 实战技巧
一、定义
Runtime 是什么?
- Objective-C 的 Runtime 实际上是 C 语言的一套函数和类库,用于支持 Objective-C 编程语言的动态性,包括类的创建、方法调用、属性访问等。
二、Runtime 实战技巧
1.Method Swizzling(方法交换)
1. 什么是 Method Swizzling?
- 定义:在程序运行时,交换两个方法实现的过程。
- 本质:修改方法对应的
IMP
(指向函数实现的指针)。 - 常用场景:
- 给系统方法加功能
- 替换系统方法行为
- AOP
2. 如何实现 Swizzling?
常用 API:
1 | Method class_getInstanceMethod(Class cls, SEL name); |
代码示例:
1 | @implementation UIViewController (Swizzling) |
3. 注意事项
+load
和dispatch_once
保证只执行一次。- 确保方法存在,避免引起程序崩溃。
- 注意继承关系和子类覆盖问题。
2.消息转发机制
1. 什么是消息转发?
- 当对象收到无法响应的消息,有三次机会处理:
- 动态方法解析(
resolveInstanceMethod:
) - 备用接收者(
forwardingTargetForSelector:
) - 完整消息转发(
forwardInvocation:
)
- 动态方法解析(
2. 详细流程
(1)动态方法解析
1 | + (BOOL)resolveInstanceMethod:(SEL)sel |
示例:
1 | + (BOOL)resolveInstanceMethod:(SEL)sel { |
(2)快速转发
1 | - (id)forwardingTargetForSelector:(SEL)aSelector |
示例:
1 | - (id)forwardingTargetForSelector:(SEL)aSelector { |
(3)完整消息转发
1 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector |
示例:
1 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { |
3. 关联对象 (Associated Objects)
定义:
在不改动原类的基础上,给分类、扩展类增加实体属性。
常用 API:
objc_setAssociatedObject
objc_getAssociatedObject
应用场景:
- UIButton 增加防重复点击时间间隔
4. 动态生成类和方法
常用 API:
objc_allocateClassPair
class_addMethod
应用场景:
- KVO 内部原理(创建子类)
- ORM框架自动生成对应属性
5. 完全自动 NSCoding 完成
思路:
通过 Runtime 遍历 ivar,自动 encode/decode,无需手写代码。
应用场景:
- Model 持久化
- 简化用户数据处理
6. 实现安全防护(防 Crash)
思路:
更换 NSArray, NSDictionary, NSString 等类的方法,防止路径错误、索引越界等 crash。
应用场景:
- 安全性框架 SafeKit
- 项目安全级别提升
7. 数据模型实体化(完全自动化等)
思路:
通过 property 列表,根据 key-value 将 JSON 转为 Model,或反转。
应用场景:
- YYModel
- MJExtension
8. 方法替换 IMP
应用:
- hook 系统方法,并加自己逻辑
- 控制方法执行流程
示例:
- 在 App 进入前后台时,统一管理封装
9. 高级消息转发 (NSProxy)
应用:
- 弱代理(避免循环引用、Timer 泄漏)
- 多代理同步分发
- 性能强化
三、实战案例
案例1:防止 UIButton 重复点击
背景
- UIButton 连续点击容易造成接口重复请求。
- 想给所有 UIButton 加防抖,不改动现有业务代码。
思路
用 Method Swizzling,替换 sendAction:to:forEvent: 方法,在里面加节流逻辑。
代码示例
UIButton+AntiRepeat.h
1 |
|
UIButton+AntiRepeat.m
1 |
|
使用
1 | UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem]; |
讲解要点
- sendAction:to:forEvent: 是 UIButton 处理点击事件的入口。
- 通过 Swizzling,在不改业务代码的情况下统一加了防抖。
- 使用 objc_setAssociatedObject 给分类动态添加属性,记录最后一次点击时间。
案例2:用 NSProxy 实现弱代理(防止 Timer 循环引用)
- NSProxy 作为中介
- 弱持 target
背景
- NSTimer 默认强引用 target,容易造成内存泄漏(循环引用)。
思路
使用 NSProxy 中转,让 Timer 弱引用真正的 target。
代码示例
TimerWeakProxy.h
1 |
|
TimerWeakProxy.m
1 |
|
使用
1 | self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 |
讲解要点
- TimerWeakProxy 持有 target 的弱引用,防止循环引用。
- forwardingTargetForSelector: 提供快速消息转发,提高性能。
案例3:自动 JSON -> Model 映射
- 利用 property 列表遍历
- 高效方便,性能好
案例4:安全防护套件
- NSArray、NSDictionary 进行方法替换,防止错误操作
四、完整思维图
技术 | 作用 | 具体场景 |
---|---|---|
Method Swizzling | 修改系统方法 | AOP 打点 |
消息转发 | 动态处理未知方法 | 弱代理 |
Associated Object | 扩展分类属性 | UIButton 防重点 |
自动 NSCoding | 自动化存储连贯 | Model 持久化 |
NSProxy | 高级转发 | Timer 弱代理 |
五、常考问题
- 说说你对 Runtime 的理解?
- Runtime 是 OC 的运行时系统,允许程序在运行时进行对象操作(如消息发送、方法交换、类创建等)。
- 动态性是 Objective-C 最重要的特性之一,基于 Runtime 实现。
- 什么是 Method Swizzling?使用时注意什么?
- 定义:交换两个方法的实现。
- 注意:
- 使用 dispatch_once 保证只交换一次。
- 确认方法存在再交换,避免崩溃。
- 维护好调用链,防止死循环。
- 避免过度使用,破坏封装性。
- Method Swizzling 常用在哪些场景?
- 统一加日志打点(比如 UIViewController 生命周期)
- 替换系统方法功能(如统计点击事件)
- AOP 编程实践
- 框架内部增强(如 AFNetworking 的某些 hook)
- 消息发送流程是怎样的?
- 调用方法 -> 转成 objc_msgSend 发送消息。
- 如果找不到方法:
- 动态方法解析(resolveInstanceMethod:)
- 快速转发(forwardingTargetForSelector:)
- 完整消息转发(forwardInvocation:)
- 最后抛出 unrecognized selector sent to instance 异常。
- 消息转发和代理模式有什么关系?
- 代理(delegate)本质就是消息转发的一种应用。
- 可以通过 forwardingTargetForSelector: 把不支持的方法交给代理对象执行。
- 复杂代理模式(如 NSProxy 多代理)就是用完整消息转发实现的。
- Swizzling 和 Hook 的区别?
- Swizzling:指“方法交换”,主要改变对象的方法实现。
- Hook:更广义,指拦截并修改程序执行过程,可以是方法层面、函数层面(Fishhook)、甚至是汇编指令层面。
- 为什么 +load 方法适合做 Swizzling?
- +load 是在类(或分类)被加载到内存时立即调用的,优先于 main()。
- 保证在对象使用前就完成方法交换。
补充:如果想更灵活控制,某些框架(如 Aspects)会在 +initialize 做 Swizzling,但要注意 initialize 只会在第一次发送消息前调用。
- 使用 Runtime 有哪些风险?
- 方法交换过多,容易导致难以维护和调试。
- 消息转发链过长,可能导致性能下降。
- 动态添加方法时参数类型签名不正确,可能导致运行时崩溃。
- 无法被编译器静态检查,出错时很难定位。
- NSProxy 是什么?为什么需要它?
- NSProxy 是一个轻量级的基类,专门用于实现消息转发。
- 典型场景:
- 远程方法调用(RPC)
- 多代理管理(如 YYTextKeyboardManager)
- 相比 NSObject,NSProxy 没有实例变量,直接走完整消息转发流程。