上一节在描述Method数据结构时,区分了SEL和IMP。知道了在OC中是通过发送消息来执行代码的。
消息发送的流程也只有两步:
- 通过SEL查找IMP。
- 执行IMP。
那么SEL是怎样去查找IMP的呢?如果找不到会怎么办?
1.1 首先会在本类找,如果找不到则会在其继承链上寻找。
1.2 在动态绑定的方法中寻找IMP。
1.3 消息转发。
由于前面两步都没有提供回调,所以只需要将注意力放在第三步消息转发上面。
消息转发又分成3个步骤:
1.3.1 动态方法解析
1.3.2 备援接收者
1.3.3 完整消息转发
一个完整SEL查找IMP的过程如图:
动态方法解析
在没有找到IMP时,首先会调用下面的方法:
//类方法
+ (BOOL)resolveClassMethod:(SEL)sel
//实例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
在这个方法中,只需要给对象动态添加一个方法并且返回YES,表明可以处理。
//自定义的C函数。
void messageBirthDate(){
NSLog(@"CType :MessageForward birthDate");
}
//@param sel 方法的选择子
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if ([@"messageBirthDate" isEqualToString:NSStringFromSelector(selector)]){
//添加方法
//@param class 对象的类
//@param sel 选择子
//@param IMP IMP
//@param types 参数和返回类型编码
class_addMethod([self class], selector, (IMP)messageBirthDate, "vv");
return YES;
}
return [super resolveInstanceMethod:selector];
}
备援接受者
如果动态方法解析没有进行处理,则会调用下面的方法,看能否进行处理:
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(birthDate))
{
return [SonClass new];
}
return [super forwardingTargetForSelector:aSelector];
}
这个方法实际上是将消息转发到有对应SEL的类去进行处理。
完整的消息转发
如果备援接受者依然没有进行处理,则会进行完整的消息转发。完整的消息转发其实也就只有2步:
1.获取方法签名
2.执行函数调用相关内容。
//////////////消息转发
//1.获取方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
FatherClass *father = [[FatherClass alloc] init];
signature = [father methodSignatureForSelector:@selector(birthDate)];//将fatherBirthDate的Signature转成 → birthDate的Signature
}
return signature;
}
//2.执行函数调用相关
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
//自己新组成一个invokation。
// NSMethodSignature *signature= [anInvocation methodSignature];//可以从前面anInvocation中去取。
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"@@:"];//也可以自己去生成。
NSInvocation *newInvocation =[NSInvocation invocationWithMethodSignature:signature];
FatherClass *father =[[FatherClass alloc] init];
[newInvocation setTarget:father];
[newInvocation setSelector:@selector(birthDate)];
[newInvocation invoke];
id retValue;
[newInvocation getReturnValue:&retValue];
NSLog(@"%@",(NSString *)retValue);
[anInvocation setReturnValue:&retValue];
}
这里需要注意变量的作用域,在setTarget时,newInvocation不会去持有Target。所以需要自己控制变量的作用域。
在消息转发过程中,越靠后消息处理的成本也越大,但是能够进行的掌控也越多。至于需要在哪个部分进行截获处理,就需要按照具体需求来判断了。
异常处理
当完整详细转发还不能够处理时,则会走到异常处理了:
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
[super doesNotRecognizeSelector:aSelector];
}
在异常处理中,可以做一些异常上报的工作。
总结
1.介绍了SEL寻找IMP的过程。
2.介绍了消息转发的过程。
参考: