OC消息转发机制
在OC中,消息与方法的真正实现是在执行阶段绑定的。
消息转发
编译器会将消息转发成对objc_msgSend方法的调用。
objc_msgSend方法含有两个必要的参数:receiver、selector,如:
[receiver message] 将被转换为objc_msgSend(receiver,selector);
objc_msgSend方法也能收到message的参数,如
objc_msgSend(receiver, selector, arg1, arg2, …);
objc_msgSend方法会做按照顺序进行以下操作,以完成动态绑定:
- 查找selector所指代的程序(方法的真正实现)。因为不同类对同一方法有不同的实现,所以对方法的真正实现的查找依赖于receiver的类
- 调用该实现,并将一系列参数传递过去
- 将该实现的返回值作为自己的返回值,返回之
消息传递的关键是,编译器构建每个类和对象时所采用的数据结构。每个类都包含以下两个必要的元素:
- 一个指向父类的指针
- 一个调度表(dispatch table)。该调度表讲啊类的selector与方法的实际内存地址关联起来。
类的底层构造如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //isa指针
#if !__OBJC2__ Class
super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息
long instance_size OBJC2_UNAVAILABLE; // 类占据的内存大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存列表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE;
- 每个对象都有一个指向所属类的指针isa。通过该指针,对象可以找到它所属的类,也就找到了其全部父类。
- 当向一个对象发送消息时,objc_msgSend方法根据对象的isa指针找到对象的类,然后在类的调度表(dispatch table)中查找selector。如果无法找到selector,objc_msgSend通过指向父类的指针找到父类,并在父类的调度表(dispatch table)中查找selector,以此类推直到NSObject类。一旦查找到selector,objc_msgSend方法根据调度表的内存地址调用该实现。 通过这种方式,message与方法的真正实现在执行阶段才绑定。
- 为了保证消息发送与执行的效率,系统会将全部selector和使用过的方法的内存地址缓存起来。每个类都有一个独立的缓存,缓存包含有当前类自己的 selector以及继承自父类的selector。查找调度表(dispatch table)前,消息发送系统首先检查receiver对象的缓存。
动态方法决议与消息转发
objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行。如果在层层的寻找中,均未找到方法的实现,就是会抛出unrecognized selector sent to XXX
的异常,导致程序崩溃。
但是,在crash之前,OC的运行时系统会先经过以下两个步骤:
1、Dynamic Method Resolution(动态方法决议)
首先,如果调用的方法是实例方法,OC的运行时会调用+ (BOOL)resolveInstanceMethod:(SEL)sel
,如果是类方法,则会调用+ (BOOL)resolveClassMethod:(SEL)sel
让我们可以在程序运行时动态的为一个selector提供实现,如果我们添加了函数的实现,并返回YES,运行时系统会重启一次消息的发送过程,调用动态添加的方法。例如,下面的例子:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"未实现实例方法: %@",NSStringFromSelector(sel));
if (sel == @selector(testMessage)) {
class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "V@:");
}
return [super resolveInstanceMethod:sel];
}
void dynamicMethodIMP(id target, SEL sel) {
NSLog(@"动态添加实例方法 %s", __PRETTY_FUNCTION__);
}
class_addMethod 方法动态的添加新的方法与对应的实现,如果调用了[test testMessage],将会转到动态添加的dynamicMethodIMP 方法中。Objective-C的方法本质上是一个至少包含两个参数(id self, SEL _cmd)的C函数
,这样,当重启消息发送时,就能在类中找到@selector(testMessage)了。而如果方法返回NO时,将会进入下一步:消息转发(Message Forwarding)
2、Message Forwarding(消息转发)
消息转发分为两步:
- 首先运行时系统会调用
- (id)forwardingTargetForSelector:(SEL)aSelector方法
,如果这个方法中返回的不是nil或者self,运行时系统将把消息发送给返回的那个对象 - 如果
- (id)forwardingTargetForSelector:(SEL)aSelector
返回的是nil或者self,运行时系统首先会调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
方法来获得方法签名,方法签名记录了方法的参数和返回值的信息,如果-methodSignatureForSelector
返回的是nil, 运行时系统会抛出unrecognized selector exception
,程序到这里就结束了
实现多播代理
#import <Foundation/Foundation.h>
@interface MutipleDelegate : NSObject
/** 不增加这个对象的引用计数 */
@property (nonatomic, strong) NSPointerArray *weakRefTagets;
/** 添加一个代理对象 */
- (void)addDelegate:(id)delegate;
@end
#import "MutipleDelegate.h"
@implementation MutipleDelegate
- (void)addDelegate:(id)delegate {
[self.weakRefTagets addPointer:(__bridge void * _Nullable)(delegate)];
}
- (NSPointerArray *)weakRefTagets {
if (_weakRefTagets == nil) {
_weakRefTagets = [NSPointerArray weakObjectsPointerArray];
}
return _weakRefTagets;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *sign = [super methodSignatureForSelector:aSelector];
if (sign == nil) {
for (id target in self.weakRefTagets) {
if ((sign = [target methodSignatureForSelector:aSelector])) {
break ;
}
}
}
return sign;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
for (id target in self.weakRefTagets) {
if ([target respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:target];
}
}
}
- (BOOL)respondsToSelector:(SEL)aSelector {
if ([super respondsToSelector:aSelector]) {
return YES;
}
for (id target in self.weakRefTagets) {
if ([target respondsToSelector:aSelector]) {
return YES;
}
}
return NO;
}
@end