面试题答案
一键面试性能瓶颈分析
- 动态方法解析性能影响:
- 在Objective - C中,当调用一个对象上不存在的方法时,会启动动态方法解析。在大型项目中频繁使用协议模拟多继承,可能会导致大量方法调用触发动态方法解析。因为协议方法不一定在类的编译期就确定实现,运行时需要动态查找并绑定方法实现,这涉及到在运行时的方法列表中进行搜索,增加了额外的时间开销。
- 例如,假设有一个类遵循多个协议,每个协议都有一些方法。当调用其中一个协议方法时,如果该类没有直接实现,就需要动态解析,从相关的类簇或其他可能提供实现的地方查找方法,这会增加调用方法的延迟。
- 消息转发性能影响:
- 如果动态方法解析没有找到对应的方法实现,就会进入消息转发阶段。消息转发分为备用接收者和完整转发两个阶段。在大型项目中,由于协议的广泛使用,可能会频繁触发消息转发。
- 备用接收者阶段,系统会尝试寻找其他对象来处理这个消息。这需要遍历继承体系等查找备用接收者,增加了时间复杂度。而完整转发阶段,需要创建一个NSInvocation对象,封装消息的所有信息,包括方法选择器、参数等,然后再寻找合适的对象来处理这个消息,这个过程涉及到内存分配和复杂的操作,严重影响性能。例如,每次完整转发都要创建和配置NSInvocation对象,在高频率调用场景下,会造成显著的性能开销。
优化策略及底层运行时机制解释
- 提前实现协议方法:
- 策略:在类的设计阶段,尽量提前为协议中的方法提供实现,避免在运行时触发动态方法解析和消息转发。
- 底层运行时机制解释:在Objective - C运行时,方法调用是基于方法选择器(SEL)在类的方法列表中查找对应的IMP(函数指针)。如果类在编译期就实现了协议方法,那么在调用时可以直接根据方法选择器快速找到对应的IMP并执行,跳过了动态方法解析和消息转发的复杂过程,大大提高了方法调用的效率。例如,一个类遵循了
UITableViewDataSource
协议,在类定义时就实现了tableView:numberOfRowsInSection:
等方法,调用这些方法时就直接执行实现代码,而不会触发动态查找。
- 使用类扩展(Class Extension):
- 策略:通过类扩展为类添加私有方法来实现协议方法。类扩展可以在编译期将方法声明和实现关联起来,避免运行时的动态查找。
- 底层运行时机制解释:类扩展是在编译期将方法声明和实现合并到类的结构中。运行时,类的方法列表包含了类扩展中实现的方法,调用协议方法时,直接在类的方法列表中根据方法选择器查找对应的IMP,无需经过动态方法解析和消息转发。例如,在
MyViewController.m
文件中,通过类扩展为MyViewController
类实现MyProtocol
协议的方法,运行时这些方法就像类的普通方法一样直接被调用。
- 缓存方法调用:
- 策略:对于频繁调用的协议方法,可以在类中缓存方法的IMP。例如,在类的初始化方法中获取协议方法的IMP并保存,后续调用时直接使用缓存的IMP。
- 底层运行时机制解释:运行时通过
class_getInstanceMethod
等函数可以获取类中某个方法的IMP。缓存IMP后,调用协议方法时,直接使用缓存的IMP,绕过了运行时动态查找方法IMP的过程。这减少了动态方法解析和消息转发的可能性,提高了性能。例如,对于一个经常调用的协议方法updateData
,在类的init
方法中获取其IMP并保存为成员变量,每次调用updateData
时直接通过缓存的IMP调用,避免了重复的动态查找。