Runtime机制中最重要的特性是消息处理机制。先了解一下与方法相关的一些概念。
SEL:又叫选择器,是一个方法的selector的指针,定义如下:
typedef struct objc_selector *SEL;
selector实际就是方法名,它是在编译时根据方法的方法名,参数序列生成一个标示符,这个标示符就是SEL。两个类不管是否存在继承关系,只要方法名相同,那么方法的SEL就是一样的。
IMP:实际上是一个函数指针,Runtime的消息传递最终执行IMP指向的函数,定义如下:
id (*IMP)(id, SEL, ...)
第一个参数指向self的指针(如果是实例方法则指向类实例的内存地址,如果是类方法则指向元类指针),第二个参数是方法选择器,后面就是参数列表。
Method:用于表示类定义中的方法,则定义如下:
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE; // 方法名
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE; // 方法实现
}
该结构体中包含一个SEL和IMP,实际上相当于在SEL和IMP之间作了一个映射。
方法调用流程:在Objective-C中,消息直到运行时才绑定到方法实现上。编译器会将消息表达式[receiver message]转化为一个消息函数的调用,即objc_msgSend。这个函数将消息接收者和方法名作为其基础参数,如以下所示:
objc_msgSend(receiver, selector)//第一个参数消息接收对象,第二个参数方法的selector
objc_msgSend(receiver, selector, arg1, arg2, ...)
当消息发送给一个对象时,objc_msgSend通过对象的isa指针获取到类的结构体,然后在方法分发表里面查找方法的selector。如果没有找到selector,则通过objc_msgSend结构体中的指向父类的指针找到其父类,并在父类的分发表里面查找方法的selector。依此,会一直沿着类的继承体系到达NSObject类。一旦定位到selector,函数会就获取到了实现的入口点,并传入相应的参数来执行方法的具体实现。如果最后没有定位到selector,则会走消息转发流程。为了加速消息的处理,运行时系统缓存使用过的selector及对应的方法的地址。
消息转发:消息转发机制基本分为三个步骤。
(1)动态方法解析
(2)消息重定向
(3)完整转发
动态方法解析意思是当对象在接收到未知消息时,首先调用所属类的类方法+resolveInstanceMethod:(实例方法)或者+resolveClassMethod:(类方法)。在这个方法中,我们有机会为该未知消息新增一个”处理方法””。不过使用该方法的前提是我们已经实现了该”处理方法”,只需要在运行时通过class_addMethod函数动态添加到类里面就可以了。如下代码所示:
+ (BOOL)resolveInstanceMethod:(SEL)sel{//动态方法解析
NSLog(@"%s",__func__);
if (sel == @selector(run)) {
return class_addMethod([self class],sel,(IMP)run,"v@:");
}
return [super resolveInstanceMethod:sel];
}
void run(id self,SEL _cmd){
NSLog(@"%s",__func__);
}
Object *obj = [Object new];
[obj performSelector:@selector(run)];
消息重定向:如果上一步无法处理消息,则runtime会调用以下方法
- (id)forwardingTargetForSelector:(SEL)aSelector
如果一个对象实现了这个方法,并返回一个非nil的结果,则这个对象会被作为消息的新接收者。如果没有相应指定对象来处理aselector,则调用其父类的实现来返回处理结果。
@interface ForwardingTargeter : NSObject
- (void)method;
@end
@implementation ForwardingTargeter
- (void)method {
NSLog(@"%@, %p", self, _cmd);
}
@end
@implementation Father
{
ForwardingTargeter *_forwardingTargeter;
}
- (instancetype)init
{
self = [super init];
if (self) {
_forwardingTargeter = [[ForwardingTargeter alloc]init];
}
return self;
}
- (void)test {
[self performSelector:@selector(method)];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSString *selStr = NSStringFromSelector(aSelector);
if ([selStr isEqualToString:@"method"]) {
return _forwardingTargeter;
}
return [super forwardingTargetForSelector:aSelector];
}
完整消息转发:如果上一步不能处理,则启用完整的消息转发机制。
- (void)forwardInvocation:(NSInvocation *)anInvocation
//NSInvocation一种直接调用对象消息的方式,和performSelector类似但可以实现相对复杂的功能,运行时系统会在这一步给消息接收者最后一次机会将消息转发给其它对象。对象会创建一个表示消息的NSInvocation对象,把与尚未处理的消息有关的全部细节都封装在anInvocation中,包括selector,目标(target)和参数。我们可以在forwardInvocation方法中选择将消息转发给其它对象。
在forwardInvocation之前需要重写以下方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([ForwardingTargeter instancesRespondToSelector:aSelector]) {
signature = [ForwardingTargeter instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([ForwardingTargeter instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:_forwardingTargeter];
}
}
以上就是Runtime中消息发送和转发的基本机制。这也是Runtime的强大之处,通过它,我们可以为程序增加很多动态的行为,虽然我们在实际开发中很少直接使用这些机制(如直接调用objc_msgSend),但了解它们有助于我们更多地去了解底层的实现。