1,消息机制
消息机制是OC动态性的体现, 相比于c语言的函数调用在编译的时候已经确定,在OC中每一个方法的实际调用需要等到运行时才能确定。 并且OC中调用方法都可以看做是向调用者发送了一条消息。消息有“名称”或“选择子(selector)”之说。消息可以接受参数,而且还可以有返回值。
例如 id object = [someObject messageName:parameter];
someObject叫做消息的接受者(方法的调用者)
messageName: 叫做方法名或者selector
parameter叫做参数
selector和参数组合起来叫做消息,调用方法就是向调用者发送一条消息。
消息调用到底是怎么执行的呢?其实OC经过编译后方法调用都会转化为下面的c语言函数调用
void objc_msgSend(id self, SEL cmd, ...)
这是一个可变参数的函数, 其中第一个参数是方法的调用者id类型,第二个参数SEL类型表示当前调用的方法名,之后是可变参数,如果方法需要参数后面会有相应的参数。
2,objc_msgSend消息派发的详细流程
objc_msgSend函数的作用就是根据self和SEL查找方法的IMP,然后调用,具体流程如下,(以调用一个对象的实例方法为例)
- 首先判断receiver是否为nil, 如果为nil,直接return
- 根据receiver的isa指针找到所属的类对象,在类对象的方法缓存cache中根据SEL查找IMP,如果查到到IMP,调用IMP,结束调用。如果没有找到,进行下一步。
- 如果在类对象的方法缓存中没有命中SEL, 就会查找类对象的方法列表,如果命中SEL, 则调用IMP并且把SEL和IMP添加到类对象的方法缓存中然后结束调用。如果方法类表中没有查找到,进行下一步
- 根据对象的superClass,到父类对象中重复上面两个步骤, 如果命中SEL,则把SEL和IMP缓存的类对象的方法缓存中,然后调用IMP结束调用。如果还是没有命中那就继续沿着superClass父链继续查找,知道superClass为nil。
- 如果直到superClass为nil还是没有找到对应的SEL,就会走消息转发的流程
2,消息转发流程
消息转发大致分为三步: 动态方法解析、备用消息接受者和完整的消息转发。
(1)动态方法解析
其中动态方法解析类提供一个机会就是可以添加方法,前提是方法的实现已经写好。相关API如下:
处理没有实现的类方法
+ (BOOL)resolveClassMethod:(SEL)sel
处理没有实现的实例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
这两个方法都是类方法,并且参数是SEL,返回值为BOOL类型表示是否添加方法的实现,默认返回NO表示没有添加, 如果添加了方法的实现可以返回YES
注意,动态方法解析时, 处理类方法和实例方法时,添加方法的时候添加方法的主体要搞清楚, 添加实例方法添加到类对象中,添加类方法添加到元类对象中
典型实现
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically))
{// 判断如果是需要处理的sel则添加实现
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
// 不能阻断父类的响应
return [super resolveInstanceMethod:aSel];
}
(2) 备用接收者
当动态方法解析失败, 没有给相应的sel添加实现的时候, 就会进入消息转发流程, 而消息转发流程第一步就是备用接收者,可以返回一个对象,让对象去处理消息,如果返回的对象不为nil,消息派发系统将继续消息派发到新接受者,注意不要返回self,否则会陷入死循环
如果在子类中实现该方法, 注意如果没有添加处理时不要阻断父类的处理,需要调用父类的该方法
该方法可以在更昂贵的消息转发前重定向消息到指定的对象,如果转发的目标是捕获NSInvocation,或者在转发过程中操作参数或返回值,那么它是没有用的。
该方法是转发实例方法,以SEL为参数,返回值是id类型表示要转发的对象,默认返回nil,
- (id)forwardingTargetForSelector:(SEL)aSelector
相应的也有转发类方法
+ (id)forwardingTargetForSelector:(SEL)aSelector
(3) 完整的消息转发
如果前两步都没有完成方法,就会进入完整的消息转发流程。首先会调用
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
或者
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
该方法会根据SEL,返回一个方法描述的对象,如果指定的sel在当前实例或者类中没有找到,就返回nil (抛出异常crash),该方法用于创建NSInvocation对象。
// 上一步方法签名创建好之后系统会自动封装本次调用的方法信息,创建NSInvocation对象, 然后调用forwardInvocation:方法,最后一次机会进行处理。
- (void)forwardInvocation:(NSInvocation *)anInvocation
forwardInvocation:进行处理,这一步的处理可以直接转发给其它对象,即和第二步的效果等效,但是很少有人这么干,因为消息处理越靠后,就表示处理消息的成本越大,性能的开销就越大。所以,在这种方式下,会改变消息内容,比如增加参数,改变选择子等等。
代码如下
/// 完整的消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSString * selStr = NSStringFromSelector(anInvocation.selector);
if ([selStr isEqualToString:@"playPiano"]) {
anInvocation.selector = NSSelectorFromString(@"travel:");
/// 方法的第一个参数是self。第二个参数是sel。后面是参数
NSString *paramStr = @"测试阿斯顿发";
[anInvocation setArgument: ¶mStr atIndex:2];
// 也可以设置其他对象调用
[anInvocation invokeWithTarget:self];
return;
}
[super forwardInvocation:anInvocation];
}
/// 返回一个方法的类型描述
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%@",NSStringFromSelector(aSelector));
NSString *method = NSStringFromSelector(aSelector);
if ([@"playPiano" isEqualToString:method]) {
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
return signature;
}
return nil;
}
如果在上述三步中都没有进行相应的处理,那么就会抛出异常crash并且报经典错误unrecognized selector sent to instance
网友评论