我们知道,在 Objective-C
中,所有的 [receiver message]
都会转换为 objc_msgSend(receiver, @selector(message))
调用,
而 objc_msgSend
的调用又涉及到方法查找、消息动态处理,消息转发等过程。下面我们结合 objc 的源码来深入了解 Objective-C 的方法调用流程。
在开始前我们需要了解几个概念:SEL,IMP,Method:
1. 概念:
1.1 SEL
在objc.h
中,SEL如下定义
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
1.2 IMP
在objc.h
中,IMP如下定义
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id (*IMP)(id, SEL, ...);
#endif
1.3 Method
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
Method = SEL + IMP + method_types
,相当于在SEL和IMP之间建立了一个映射。
2. Objective-C 方法调用流程
参考 objc_msgsend-part-1-the-road-map,通过对objc_msgSend
的汇编源码分析,总结出以下流程:
- 检查selector是否需要忽略
- 检查target是否为nil:
- 如果设置了nil处理函数,跳转到对应函数
- 如果没有则直接清空返回
- 从target的class中根据selector查找IMP并跳转到对应实现
2.1 寻找IMP流程
- 在当前class的缓存列表(
cache methodLists
)中寻找,如果找到则跳到对应实现;否则继续执行 - 从当前class的方法列表(
methodLists
)中查找,找到了添加到缓存列表并跳转到对应实现;否则继续执行 - 从父类的缓存列表及方法列表查找,直到找到NSObject为止,找到了添加到缓存列表并跳转到对应实现;否则继续执行
- 以上步骤找不到IMP,则进入
消息动态处理
和消息转发
流程
2.2 消息动态处理及消息转发流程
-
+ (BOOL)resolveInstanceMethod:(SEL)sel
方法动态决议,利用runtime的特性动态添加方法
void dynamicMethodIMP(id self, SEL _cmd)
{
// implementation ....
}
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically))
{
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSel];
}
-
- (id)forwardingTargetForSelector:(SEL)aSelector;
将消息转发给代理对象处理
- (id)forwardingTargetForSelector:(SEL)aSelector
{
//如果代理对象能处理,则转接给代理对象
if ([proxyObj respondsToSelector:aSelector]) {
return proxyObj;
}
//不能处理进入转发流程
return nil;
}
- 消息转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation
注意:如果
methodSignatureForSelector
返回nil则不会继续执行forwardInvocation
,转发流程终止,抛出无法处理的异常。
如果 methodSignatureForSelector
返回方法签名非nil,我们还有最后一次机会处理这个消息,那就是在forwardInvocation
回调里进行消息转发,具体如下:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *sig = [friend methodSignatureForSelector:@selector(bb_dealNotRecognizedMessage:)];
return sig;
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
SEL aSelector = [invocation selector];
if ([friend respondsToSelector:aSelector])
[invocation invokeWithTarget:friend];
else
[super forwardInvocation:invocation];
}
2.3 . 消息异常处理: - (void)doesNotRecognizeSelector:(SEL)aSelector;
完整流程如下