面试题答案
一键面试1. 发送消息
在Objective-C中,当向一个对象发送消息时,比如[obj message]
,编译器实际会将其转化为objc_msgSend
函数的调用。objc_msgSend
函数第一个参数是接收者对象(即obj
),第二个参数是方法选择器(@selector(message)
),后面跟着方法的参数。
2. runtime数据结构
- 类结构(
objc_class
):每个类在runtime中都有一个objc_class
结构体来表示。它包含了类的元数据,如类名、超类指针,以及一个指向方法列表(method_list_t
)的指针。 - 方法列表(
method_list_t
):是一个包含多个method_t
结构体的列表。method_t
结构体包含了方法的选择器(SEL
)、方法的实现(IMP
)以及一些其他信息。 - 缓存(
objc_cache
):每个类都有一个objc_cache
,用于缓存最近使用过的方法。缓存是一个哈希表,键为方法选择器(SEL
),值为方法的实现(IMP
)。
3. 查找方法实现的关键步骤
- 缓存查找:
objc_msgSend
首先在接收者对象类的缓存中查找方法。因为缓存采用哈希表结构,所以查找速度很快。如果在缓存中找到了对应的方法实现(IMP
),则直接调用该实现,流程结束。 - 方法列表查找:如果缓存中未找到,就会在类的方法列表中查找。runtime会遍历类的
method_list_t
,将每个method_t
的选择器(SEL
)与传入的选择器进行比较。如果找到匹配的选择器,就将该方法的实现(IMP
)缓存起来,然后调用该实现,流程结束。 - 父类查找:如果在本类的方法列表中未找到,runtime会沿着继承链向上,在父类的缓存和方法列表中重复上述查找过程,直到找到方法实现或者到达继承链顶端(
NSObject
类)。如果在NSObject
类中也未找到,就会进入动态方法解析阶段。 - 动态方法解析:runtime会给程序一次动态添加方法实现的机会。首先调用
+ (BOOL)resolveInstanceMethod:(SEL)sel
(实例方法)或+ (BOOL)resolveClassMethod:(SEL)sel
(类方法)方法。如果在这些方法中添加了方法实现,runtime会重新启动消息发送流程。如果动态方法解析没有处理该消息,则进入备用接收者阶段。 - 备用接收者:runtime会调用
- (id)forwardingTargetForSelector:(SEL)aSelector
方法,尝试找到一个备用的对象来处理该消息。如果返回了一个非nil
对象,runtime会向这个备用对象重新发送消息。如果没有找到备用接收者,则进入完整的消息转发阶段。 - 完整的消息转发:runtime会创建一个
NSInvocation
对象,包含了原消息的所有信息,然后调用- (void)forwardInvocation:(NSInvocation *)anInvocation
方法。在这个方法中,开发者可以手动指定将消息转发给哪个对象处理。如果这个方法也没有处理消息,runtime会调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
方法获取方法签名,如果获取到签名,runtime会再次调用- (void)forwardInvocation:(NSInvocation *)anInvocation
。如果还是没有处理消息,最终会抛出unrecognized selector sent to instance
的异常。