iOS运行时(runtime)探究三:消息转发
2016-08-25 19:04
489 查看
一、方法调用流程
在Objective-C中,消息直到运行时才绑定到方法实现上。编译器会将消息表达式[receiver message]转化为一个消息函数的调用,即objc_msgSend。
当消息发送给一个对象时,objc_msgSend通过对象的isa指针获取到类的结构体,然后在方法分发表里面查找方法的selector。如果没有找到selector,则通过objc_msgSend结构体中的指向父类的指针找到其父类,并在父类的分发表里面查找方法的selector。依此,会一直沿着类的继承体系到达NSObject类。一旦定位到selector,函数会就获取到了实现的入口点,并传入相应的参数来执行方法的具体实现。如果最后没有定位到selector,则会走消息转发流程。
二、消息转发
默认情况下,如果是以[object message]的方式调用方法,如果object无法响应message消息时,编译器会报错。但如果是以perform…的形式来调用,则需要等到运行时才能确定object是否能接收message消息。如果不能,则程序崩溃。不过,我们可以采取一些措施,让我们的程序执行特定的逻辑,而避免程序的崩溃。消息转发机制基本上分为三个步骤:
动态方法解析
备用接收者
完整转发
1、动态方法解析
对象在接收到未知的消息时,首先会调用所属类的类方法+resolveInstanceMethod:(实例方法)或者+resolveClassMethod:(类方法)。在这个方法中,我们有机会为该未知消息新增一个”处理方法”“。不过使用该方法的前提是我们已经实现了该”处理方法”,只需要在运行时通过class_addMethod函数动态添加到类里面就可以了。
#import <Foundation/Foundation.h> @interface MyClass : NSObject @end
NSString* mergeString(id self, SEL _cmd, NSString *str1, NSString *str2) { return [NSString stringWithFormat:@"%@%@", str1, str2]; } #pragma mark - 动态方法解析 + (BOOL)resolveInstanceMethod:(SEL)sel { //获取方法名 NSString *selectorString = NSStringFromSelector(sel); //根据方法名添加方法 if ([selectorString isEqualToString:@"mergeString:andStr:"]) { class_addMethod([self class], sel, (IMP)mergeString, "@@:@@"); } return [super resolveInstanceMethod:sel]; }
MyClass *myClass = [[MyClass alloc] init]; NSLog(@"%@", [myClass performSelector:@selector(mergeString:andStr:) withObject:@"123" withObject:@"456"]);
2、备用接收者
如果在上一步动态方法解析没有处理或无法处理消息,则Runtime会继续调方法:- (id)forwardingTargetForSelector:(SEL)aSelector,如果一个对象实现了这个方法,并返回一个非nil的结果,则这个对象会作为消息的新接收者,且消息会被分发到这个对象。当然这个对象不能是self自身,否则就是出现无限循环。当然,如果我们没有指定相应的对象来处理aSelector,则应该调用父类的实现来返回结果。
#import <Foundation/Foundation.h> @interface OtherClass : NSObject /** * 把字符串转换为数组 * * @param str 需转换的字符串 * * @return 转换好的数组 */## 标题 ## - (NSArray *)arrayWithString:(NSString *)str; @end
#import "OtherClass.h" @implementation OtherClass /** * 把字符串转换为数组 * * @param str 需转换的字符串 * * @return 转换好的数组 */ - (NSArray *)arrayWithString:(NSString *)str { if (str && (str != NULL) && (![str isKindOfClass:[NSNull class]]) && str.length > 0) { NSMutableArray *mArr = [NSMutableArray arrayWithCapacity:1]; for (NSInteger index = 0; index < str.length; index++) { [mArr addObject:[str substringWithRange:NSMakeRange(index, 1)]]; } return mArr; } return nil; } @end
在MyClass.m中添加如下方法:
#pragma mark - 备用接收者 - (id)forwardingTargetForSelector:(SEL)aSelector { //获取方法名 NSString *selectorString = NSStringFromSelector(aSelector); //根据方法名添加方法 if ([selectorString isEqualToString:@"arrayWithString:"]) { OtherClass *otherClass = [[OtherClass alloc] init]; return otherClass; } return [super forwardingTargetForSelector:aSelector]; }
调用如下代码:
MyClass *myClass = [[MyClass alloc] init]; NSLog(@"%@", [myClass performSelector:@selector(arrayWithString:) withObject:@"ee884y4hkshdf82983"]);
打印结果如下:
3.完整消息转发
如果在上一步备用接收者还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。此时会调用方法:- (void)forwardInvocation:(NSInvocation *)anInvocation,对象会创建一个表示消息的NSInvocation对象,把与尚未处理的消息有关的全部细节都封装在anInvocation中,包括selector,目标(target)和参数。我们可以在forwardInvocation方法中选择将消息转发给其它对象。
forwardInvocation:方法的实现有两个任务:
<1>定位可以响应封装在anInvocation中的消息的对象。这个对象不需要能处理所有未知消息。
<2>使用anInvocation作为参数,将消息发送到选中的对象。anInvocation将会保留调用结果,运行时系统会提取这一结果并将其发送到消息的原始发送者。
还有一个很重要的问题,我们必须重写以下方法:- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector,消息转发机制使用从这个方法中获取的信息来创建NSInvocation对象。因此我们必须重写这个方法,为给定的selector提供一个合适的方法签名。
向OtherClass.m中添加如下方法:
/** * 逆置字符串 * * @param str 需逆置的字符串 * * @return 置换后的字符串 */ - (NSString *)inverseWithString:(NSString *)str { if (str && (str != NULL) && (![str isKindOfClass:[NSNull class]]) && str.length > 0) { NSMutableString *mStr = [NSMutableString stringWithCapacity:1]; for (NSInteger index = str.length; index > 0; index--) { [mStr appendString:[str substringWithRange:NSMakeRange(index - 1, 1)]]; } return mStr; } return nil; }
向MyClass.m中添加如下方法:
#pragma mark - 完整消息转发 //必须重写这个方法,为给定的selector提供一个合适的方法签名。 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSMethodSignature *signature = [super methodSignatureForSelector:aSelector]; if (!signature) { if ([OtherClass instancesRespondToSelector:aSelector]) { //获取方法签名 signature = [OtherClass instanceMethodSignatureForSelector:aSelector]; } } return signature; } - (void)forwardInvocation:(NSInvocation *)anInvocation { //anInvocation选择将消息转发给其它对象 if ([OtherClass instancesRespondToSelector:anInvocation.selector]) { [anInvocation invokeWithTarget:[[OtherClass alloc] init]]; } }
如下方式调用代码:
MyClass *myClass = [[MyClass alloc] init]; NSLog(@"%@", [myClass performSelector:@selector(inverseWithString:) withObject:@"se239udfj48saofowe"]);
三、示例代码下载
代码下载请猛戳这里相关文章推荐
- iOS runtime探究(二): 从runtime開始深入理解OC消息转发机制
- iOS runtime探究(二): 从runtime開始深入理解OC消息转发机制
- iOS runtime探究(二): 从runtime开始深入理解OC消息转发机制
- iOS学习笔记56(Runtime)-Objective-C Runtime 运行时之三:方法与消息
- 【转来的精】iOS知识树,知识目录(包括对象、Block、消息转发、GCD、运行时、runloop、动画、Push、KVO、tableview,UIViewController、提交AppStore)
- iOS 开发 深入浅出Rumtime运行时之消息转发机制详解
- ios runtime IMP指针 消息转发机制
- Runtime 运行时之一:消息转发
- iOS运行时(runtime)探究一:重要概念
- iOS runtime 之消息转发
- IOS -运行时 (消息传递再探究)
- iOS知识树,知识目录(包括对象、Block、消息转发、GCD、运行时、runloop、动画、Push、KVO、tableview,UIViewController、提交AppStore)
- Runtime初涉之消息转发 http://www.cocoachina.com/ios/20151015/13769.html
- iOS开发-OC之知识树,知识点(包括对象、Block、消息转发、GCD、运行时、runloop、动画、Push、KVO、tableview,UIViewController、提交AppStore)
- 运行时(runtime)技术的几个要点总结 和 消息转发
- iOS runtime 消息转发
- ios runtime IMP指针 消息转发机制
- 【转】iOS知识树,知识目录(包括对象、Block、消息转发、GCD、运行时、runloop、动画、Push、KVO、tableview,UIViewController、提交AppStore)
- 【iOS】运行时消息传递与转发机制
- 简谈runtime运行机制之消息发送与转发