当我们用中括号[]
调用OC函数的时候,实际上会进入消息发送和消息转发流程:
消息发送(Messaging),runtime系统会根据
SEL
查找对用的IMP
,查找到,则调用函数指针进行方法调用;若查找不到,则进入动态消息解析和转发流程,如果动态解析和消息转发失败,则程序crash并记录日志。
在进入正题之前,我们先思考两个问题:
-
类实例可以调用类方法吗? 类可以调用实例方法吗? 为什么?
-
下面代码输出什么?
@interface Father : NSObject
@end
@implementation Father
@end
@interface Son : Father
- (void)showClass;
@end
@implementation Son
- (void)showClass {
NSLog(@"self class = %@, super class = %@", [self class], [super class]);
}
...
Son *son = [Son new];
[son showClass]; // 这里输出什么?
...
消息相关数据结构
SEL
SEL
被称之为消息选择器,它相当于一个key,在类的消息列表中,可以根据这个key,来查找到对应的消息实现。
在runtime中,SEL的定义是这样的:
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
它是一个不透明的定义,似乎苹果故意隐藏了它的实现。目前SEL仅是一个字符串。
这里要注意,即使消息的参数类型不同(注意,不是指参数数量不同)或方法所属的类也不同,但只要方法名相同,SEL
也是一样的。所以,SEL
单独并不能作为唯一的Key,必须结合消息发送的目标Class,才能找到最终的IMP
。
我们可以通过OC编译器命令@selector()
或runtime函数sel_registerName
,来获取一个SEL
类型的方法选择器。
method_t
当需要发送消息的时候,runtime会在Class的方法列表中寻找方法的实现。在方法列表中方法是以结构体method_t
存储的。
struct method_t {
SEL name;
const char *types;
IMP imp;
struct SortBySELAddress :
public std::binary_function<const method_t&,
const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};
可以看到method_t
包含一个SEL
作为key,同时有一个指向函数实现的指针IMP
。method_t
还包含一个属性const char *types;
types是一个C字符串,用于表明方法的返回值和参数类型。一般是这种格式的:
v24@0:8@16
关于SEL type,可以参考Type Encodings
IMP
IMP实际是一个函数指针,用于实际的方法调用。在runtime中定义是这样的:
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
IMP
是由编译器生成的,如果我们知道了IMP
的地址,则可以绕过runtime消息发送的过程,直接调用函数实现。关于这一点,我们稍后会谈到。
在消息发送的过程中,runtime就是根据id
和SEL
来唯一确定IMP
并调用之的。
消息
当我们用[]
向OC对象发送消息时,编译器会对应的代码修改为objc_msgSend
, 其定义如下:
OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
其实,除了objc_msgSend
,编译器还会根据实际情况,将消息发送改写为下面四个msgSend之一:
objc_msgSend
objc_msgSend_stret
objc_msgSendSuper
objc_msgSendSuper_stret
当我们将消息发送给super class的时候,编译器会将消息发送改写为**SendSuper
的格式,如调用[super viewDidLoad]
,会被编译器改写为objc_msgSendSuper
的形式。
objc_msgSendSuper
的定义如下:
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
可以看到,调用super方法时,msgSendSuper
的第一个参数不是id self
,而是一个objc_super *
。objc_super
定义如下:
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
__unsafe_unretained _Nonnull Class super_class;
};
objc_super
包含两个数据,receiver
指调用super方法的对象,即子类对象,而super_class
表示子类的Super Class。
这就说明了在消息过程中调用了 super方法和没有调用super方法,还是略有差异的。我们将会在下面讲解。
至于**msgSend
中以_stret
结尾的,表明方法返回值是一个结构体类型。
在objc_msgSend
的内部,会依次执行:
- 检测selector是否是应该忽略的,比如在Mac OS X开发中,有了垃圾回收机制,就不会响应
retain
,release
这些函数。 - 判断当前
receiver
是否为nil
,若为nil
,则不做任何响应,即向nil发送消息,系统不会crash。 - 检查
Class
的method cache,若cache未命中,则进而查找Class
的method list
。 - 若在
Class
的method list
中未找到对应的IMP
,则进行消息转发 - 若消息转发失败,程序crash
objc_msgSend
objc_msgSend