消息传递和消息转发
Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。理解 Objective-C 的 Runtime 机制可以帮我们更好的了解这个语言,适当的时候还能对语言进行扩展,从系统层面解决项目中的一些设计或技术问题。了解 Runtime ,要先了解它的核心 - 消息传递 (Messaging)。
什么是消息传递
当我们调用一个对象法法时如
[person walk];
其本质转化为C语言调用了- objc_msgSend()函数
相当于
objc_msgSend(person, @selector(walk));
当然消息传递的过程在于objc_object的isa指针和objc_class中的class_dispatch_table.
下面我们来看一下objc_object的结构体
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
它包含了Class类型的结构体:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
if !OBJC2
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
endif
} OBJC2_UNAVAILABLE;
/* Use Class
instead of struct objc_class *
*/
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
ifdef LP64
int space OBJC2_UNAVAILABLE;
endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
struct objc_method_list {
struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
ifdef LP64
int space OBJC2_UNAVAILABLE;
endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
就拿上述我们调用的[person walk]为例:
- 首先,通过person的isa指针找到他所属的类
- 在该类的方法列表class_method_list 里遍历找到该方法
- 如果该类没有找到walk方法,就去他的父类里面查找
- 如果找到了改walk方法,就去找他的方法实现IMP
- 如果最后也没有找到该方法,就会发生unrecognized selector sent to instance 0xxxxxxxxxx
调用该方法时会给该方法设置一个权重,将调用频率较高的方法缓存起来
这样下次再调用该方法的时候就会从方法缓存列表里面去查找
如上所述5:我们说如果最终没有汇总爱到该方法的时候默认会提示未找到该方法,不过作为动态语言,Runtime 给我们提供了3次拯救的机会
- Method resolution
- Fast Forwarding
- Normal forwarding
Method resolution
首先Objective-C Runtime会调用+resolveInstanceMethod: 或者 +resolveClassMethod:让你提供一个方法的实现进行替换。
实例方法调用+resolveInstanceMethod: 对象方法调用+resolveClassMethod:
如果你添加了方法的实现并返回YES的话,Runtime就会执行你所实现的方法,如下:
// 第一次拯救
// 如如果返回YES,实例方法可以在这个方法里面动态增加方法的实现
- (BOOL)resolveInstanceMethod:(SEL)sel{
if(sel == @selector(test:age:)){
class_addMethod([self class], sel, (IMP)replaceTest, "v@:");
return YES;
}
return [super resolveClassMethod:sel];
}
// 第一次拯救
// 如果返回YES,类方法可以在这个方法里面动态增加方法的实现
- (BOOL)resolveClassMethod:(SEL)sel{
if(sel == @selector(test:age:)){
class_addMethod([self class], sel, (IMP)replaceTest, "v@:");
return YES;
}
return [super resolveClassMethod:sel];
}
void replaceTest(id obj, SEL _cmd, NSString *name, int age){
NSLog(@"name : %@ age: %d", name, age);
}
Fast Forwarding
如果resove方法返回NO或者不实现该方法,Runtime就会执行下一步:消息转发。
如果实现了-forwardingTargetForSelector: Runtime就会调用这个方法,将消息转发给其他对象的机会。
这个过程需要你额外创建一个对象去实现未实现的方法
下面我在JFSafeForwarding类里面实现了该方法
// 第二次拯救
// 如果resolveXXXMethod返回NO 且目标对象实现了这个方法,Runtime会把这个消息转发给其他对象。
// 这个过程不会创建新的对象
- (id)forwardingTargetForSelector:(SEL)aSelector{
JFSafeForwarding *safeForwarding = [[JFSafeForwarding alloc] init];
if([safeForwarding respondsToSelector:aSelector]){
return safeForwarding;
}
return [super forwardingTargetForSelector:aSelector];
}
只要这个方法不是返回nil或者是self, 就会将消息转发到你所实现的方法,
否则,就会继续走第三次拯救
Normal Forwarding
这是第三次也是最后一次拯救机会。
首先他会发送 -methodSignatureForSelector: 消息获取函数的参数类型和返回值类型。
如果该方法返回nil,Runtime 会发出-doesNotRecognizeSelector: 消息,程序终止。
如果返回了一个函数签名,Runtime 就会创建一个NSInvocation对象并发送-forwardInvocation: 消息给目标对象。
NSInvocation 实际上就是对一个消息的描述,包括selector 以及参数等信息。所以你可以在 -forwardInvocation: 里修改传进来的 NSInvocation 对象,然后发送 -invokeWithTarget: 消息给它,传进去一个新的目标:
// 第三次拯救
// 如果forwardingTargetForSelector 返回nil或者self,且实现了这个方法,
// 首先它会发送 - methodSignatureForSelector:消息获取函数的参数和返回值类型,如果返回nil
// Runtime 发送 - doesNotRecognizeSelector:方法,进程结束
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if(!signature){
JFSafeForwarding *safeForwarding = [[JFSafeForwarding alloc] init];
signature = [safeForwarding methodSignatureForSelector:aSelector];
}
return signature;
}
// 如果返回了一个函数签名,Runtime 就会创建一个 NSInvocation 对象并发送 -forwardInvocation: 消息给目标对象。
- (void)forwardInvocation:(NSInvocation *)anInvocation{
SEL sel = anInvocation.selector;
JFSafeForwarding *safeForwarding = [[JFSafeForwarding alloc] init];
if([safeForwarding respondsToSelector:sel]){
[anInvocation invokeWithTarget:safeForwarding];
}else{
[self doesNotRecognizeSelector:sel];
}
}
第三次和第二次又如下雷同的地方都是借助第三方的方法实现去做消息转发,区别在于第三次动态创建了NSInvocation实例。
总结:
Objective-C 中给一个对象发送消息会经过以下几个步骤:
在对象类的 dispatch table 中尝试找到该消息。
如果找到了,跳到相应的函数IMP去执行实现代码。
如果没有找到,Runtime 会发送 +resolveInstanceMethod: 或者 +resolveClassMethod: 尝试去 resolve 这个消息。
如果 resolve 方法返回 NO,Runtime 就发送 -forwardingTargetForSelector: 允许你把这个消息转发给另一个对象;
如果没有新的目标对象返回, Runtime 就会发送 -methodSignatureForSelector: 和 -forwardInvocation: 消息。
你可以发送 -invokeWithTarget: 消息来手动转发消息或者发送 -doesNotRecognizeSelector: 抛出异常。
以上摘自
Message forwarding
Objective-c-messaging
The faster objc_msgSend
Objective-C Runtime