首页 > 代码库 > IOS 消息转发

IOS 消息转发

最近在看消息转发的资料,发现大部分都是理论知识,很少有完整的代码。现在以代码的形式形象的解释一下:

用Xcode创建一个工程

1.正常方法调用

创建一个类Person 代码如下

Person.h代码如下:

#import <Foundation/Foundation.h>@interface Person : NSObject- (instancetype)init NS_UNAVAILABLE;- (instancetype)initWithUserName:(NSString *)userName;- (void)logUserName;@end

 

Person.m代码如下:

#import "Person.h"@interface Person()@property (nonatomic, copy) NSString *userName;@end@implementation Person- (instancetype)initWithUserName:(NSString *)userName{    self = [super init];    if (self) {        _userName = [userName copy];    }    return self;}- (void)logUserName{    NSLog(@"userName = %@", self.userName);}@end

 

ViewController.m代码如下:

#import "ViewController.h"#import "Person.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view, typically from a nib.    Person *person = [[Person alloc] initWithUserName:@"小王"];    [person logUserName];}- (void)didReceiveMemoryWarning {    [super didReceiveMemoryWarning];    // Dispose of any resources that can be recreated.}@end

运行工程结果为:

2017-03-01 22:47:07.296 RunTimeDemo[24364:1776826] userName = 小王

结果正常

2. unrecognized selector 情况

把Person.m 的logUserName方法删除,此时Person.m 的代码如下:

#import "Person.h"@interface Person()@property (nonatomic, copy) NSString *userName;@end@implementation Person- (instancetype)initWithUserName:(NSString *)userName{    self = [super init];    if (self) {        _userName = [userName copy];    }    return self;}@end

运行工程会出现 如下结果

2017-03-01 23:06:25.788 RunTimeDemo[24729:1788071] -[Person logUserName]: unrecognized selector sent to instance 0x608000018e002017-03-01 23:06:25.796 RunTimeDemo[24729:1788071] *** Terminating app due to uncaught exception NSInvalidArgumentException, reason: -[Person logUserName]: unrecognized selector sent to instance 0x608000018e00

假如不在Person类中实现logUsrName这个方法,也可以让工程正常运行,此时就涉及到消息转发,Objective-C运行时会给出三次拯救程序崩溃的机会。

如下:

(1).Method resolution

objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步,消息转发(Message Forwarding)。

(2).Fast forwarding

如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。

(3).Normal forwarding

这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。

 

下面就验证一下上面所对应的函数的执行顺序:

Person.m的代码如下:

#import "Person.h"#define LOG_FunctionName  NSLog(@"%s", __func__)@interface Person()@property (nonatomic, copy) NSString *userName;@end@implementation Person- (instancetype)initWithUserName:(NSString *)userName{    self = [super init];    if (self) {        _userName = [userName copy];    }    return self;}+ (BOOL)resolveInstanceMethod:(SEL)sel{    LOG_FunctionName;    return YES;}- (id)forwardingTargetForSelector:(SEL)aSelector{    LOG_FunctionName;    return [super forwardingTargetForSelector:aSelector];}- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{    LOG_FunctionName;    return [super methodSignatureForSelector:aSelector];}- (void)forwardInvocation:(NSInvocation *)anInvocation{    LOG_FunctionName;}@end

运行结果为:

2017-03-01 23:45:21.723 RunTimeDemo[25672:1817388] +[Person resolveInstanceMethod:]2017-03-01 23:45:21.724 RunTimeDemo[25672:1817388] -[Person forwardingTargetForSelector:]2017-03-01 23:45:21.724 RunTimeDemo[25672:1817388] -[Person methodSignatureForSelector:]2017-03-01 23:45:21.724 RunTimeDemo[25672:1817388] +[Person resolveInstanceMethod:]2017-03-01 23:45:21.725 RunTimeDemo[25672:1817388] -[Person logUserName]: unrecognized selector sent to instance 0x60000001cef02017-03-01 23:45:21.729 RunTimeDemo[25672:1817388] *** Terminating app due to uncaught exception NSInvalidArgumentException, reason: -[Person logUserName]: unrecognized selector sent to instance 0x60000001cef0

运行结果正好验证了上述说明。

IOS 消息转发