面试题答案
一键面试1. 消息发送的起始点
当在Objective - C中向一个对象发送消息,例如 [obj someMethod]
,首先会进入 objc_msgSend
函数。这个函数是消息发送的入口点,它的原型如下:
id objc_msgSend(id self, SEL op, ...);
其中 self
是接收消息的对象,op
是选择器(Selector),即方法的名称。
2. 类对象与元类对象
- 类对象:每个Objective - C对象都有一个指向其类对象的指针(
isa
指针)。类对象包含了对象的实例变量布局、方法列表、属性列表等信息。例如,对于一个Person
类的实例person
,person
的isa
指针指向Person
类对象。 - 元类对象:类对象本身也是一个对象,它的
isa
指针指向元类对象。元类对象存储着类方法(即定义在类上的方法,而不是实例方法)。
3. 动态方法解析
- 第一阶段:当
objc_msgSend
开始查找方法实现时,首先在接收者对象的类的缓存(cache
)中查找。每个类都有一个方法缓存,用于加速方法查找。缓存是一个哈希表,以选择器(SEL
)作为键,方法实现(IMP
)作为值。如果在缓存中找到匹配的方法实现,就直接调用该方法,流程结束。 - 第二阶段:如果在缓存中未找到,接着会在类的方法列表(
methodLists
)中查找。类的方法列表是一个存储了所有实例方法的列表。如果在方法列表中找到方法实现,将其添加到缓存中,然后调用该方法。 - 动态方法解析:如果在类的方法列表中也未找到,就进入动态方法解析阶段。在这个阶段,Objective - C运行时系统会给类一次机会,通过调用
+ (BOOL)resolveInstanceMethod:(SEL)sel
(对于实例方法) 或+ (BOOL)resolveClassMethod:(SEL)sel
(对于类方法) 方法来动态添加方法实现。如果动态添加了方法实现,就可以找到并调用该方法,流程结束。
4. 备用接收者
如果动态方法解析没有成功添加方法实现,那么运行时会进入备用接收者阶段。系统会调用 -(id)forwardingTargetForSelector:(SEL)aSelector
方法,尝试找到一个备用的对象来处理该消息。如果找到了备用接收者,就将消息转发给该对象处理,流程结束。
5. 完整的消息转发
如果没有找到备用接收者,就进入完整的消息转发阶段。首先,运行时会创建一个 NSInvocation
对象,该对象包含了消息的所有信息,如选择器、参数等。然后,系统会调用 -(void)forwardInvocation:(NSInvocation *)anInvocation
方法。在这个方法中,开发者可以手动指定将消息转发给哪个对象处理。如果在这个方法中没有进行有效的转发,最后会调用 -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
方法,如果该方法返回 nil
,则会抛出 unrecognized selector sent to instance
异常,程序崩溃。
4. 涉及的数据结构
isa
指针:每个对象都有一个isa
指针,用于指向其所属的类对象。SEL
(选择器):本质是一个指向方法名称的字符串的typedef
,用于唯一标识一个方法。IMP
(方法实现):是一个指向具体函数的指针,该函数实现了方法的功能。- 方法缓存(
cache
):是一个哈希表结构,存储了经常调用的方法的SEL
和对应的IMP
,以加速方法查找。 - 方法列表(
methodLists
):是一个链表结构,存储了类中定义的所有方法的信息,包括SEL
和IMP
等。