IOS 消息转发
最近在看消息轉(zhuǎn)發(fā)的資料,發(fā)現(xiàn)大部分都是理論知識(shí),很少有完整的代碼?,F(xiàn)在以代碼的形式形象的解釋一下:
用Xcode創(chuàng)建一個(gè)工程
1.正常方法調(diào)用
創(chuàng)建一個(gè)類(lèi)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運(yùn)行工程結(jié)果為:
2017-03-01 22:47:07.296 RunTimeDemo[24364:1776826] userName = 小王結(jié)果正常
2. unrecognized selector 情況
把Person.m 的logUserName方法刪除,此時(shí)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運(yùn)行工程會(huì)出現(xiàn) 如下結(jié)果
2017-03-01 23:06:25.788 RunTimeDemo[24729:1788071] -[Person logUserName]: unrecognized selector sent to instance 0x608000018e00 2017-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類(lèi)中實(shí)現(xiàn)logUsrName這個(gè)方法,也可以讓工程正常運(yùn)行,此時(shí)就涉及到消息轉(zhuǎn)發(fā),Objective-C運(yùn)行時(shí)會(huì)給出三次拯救程序崩潰的機(jī)會(huì)。
如下:
(1).Method resolution
objc運(yùn)行時(shí)會(huì)調(diào)用+resolveInstanceMethod:或者 +resolveClassMethod:,讓你有機(jī)會(huì)提供一個(gè)函數(shù)實(shí)現(xiàn)。如果你添加了函數(shù),那運(yùn)行時(shí)系統(tǒng)就會(huì)重新啟動(dòng)一次消息發(fā)送的過(guò)程,否則 ,運(yùn)行時(shí)就會(huì)移到下一步,消息轉(zhuǎn)發(fā)(Message Forwarding)。
(2).Fast forwarding
如果目標(biāo)對(duì)象實(shí)現(xiàn)了-forwardingTargetForSelector:,Runtime 這時(shí)就會(huì)調(diào)用這個(gè)方法,給你把這個(gè)消息轉(zhuǎn)發(fā)給其他對(duì)象的機(jī)會(huì)。 只要這個(gè)方法返回的不是nil和self,整個(gè)消息發(fā)送的過(guò)程就會(huì)被重啟,當(dāng)然發(fā)送的對(duì)象會(huì)變成你返回的那個(gè)對(duì)象。否則,就會(huì)繼續(xù)Normal Fowarding。 這里叫Fast,只是為了區(qū)別下一步的轉(zhuǎn)發(fā)機(jī)制。因?yàn)檫@一步不會(huì)創(chuàng)建任何新的對(duì)象,但下一步轉(zhuǎn)發(fā)會(huì)創(chuàng)建一個(gè)NSInvocation對(duì)象,所以相對(duì)更快點(diǎn)。
(3).Normal forwarding
這一步是Runtime最后一次給你挽救的機(jī)會(huì)。首先它會(huì)發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類(lèi)型。如果-methodSignatureForSelector:返回nil,Runtime則會(huì)發(fā)出-doesNotRecognizeSelector:消息,程序這時(shí)也就掛掉了。如果返回了一個(gè)函數(shù)簽名,Runtime就會(huì)創(chuàng)建一個(gè)NSInvocation對(duì)象并發(fā)送-forwardInvocation:消息給目標(biāo)對(duì)象。
?
下面就驗(yàn)證一下上面所對(duì)應(yīng)的函數(shù)的執(zhí)行順序:
Person.m的代碼如下:
#import "Person.h"#define LOG_ClassAndFunctionName NSLog(@"%@ %s", [self class], __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_ClassAndFunctionName;return [super resolveInstanceMethod:sel]; }- (id)forwardingTargetForSelector:(SEL)aSelector {LOG_ClassAndFunctionName;return [super forwardingTargetForSelector:aSelector]; }- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {LOG_ClassAndFunctionName;return [super methodSignatureForSelector:aSelector]; }- (void)forwardInvocation:(NSInvocation *)anInvocation {LOG_ClassAndFunctionName;[super forwardInvocation:anInvocation];}- (void)doesNotRecognizeSelector:(SEL)aSelector {LOG_ClassAndFunctionName;[super doesNotRecognizeSelector:aSelector]; }@end?
運(yùn)行結(jié)果為:
2017-03-02 10:16:37.855 RunTimeDemo[35098:2456660] Person +[Person resolveInstanceMethod:] 2017-03-02 10:16:37.856 RunTimeDemo[35098:2456660] Person -[Person forwardingTargetForSelector:] 2017-03-02 10:16:37.856 RunTimeDemo[35098:2456660] Person -[Person methodSignatureForSelector:] 2017-03-02 10:16:37.857 RunTimeDemo[35098:2456660] Person +[Person resolveInstanceMethod:] 2017-03-02 10:16:37.858 RunTimeDemo[35098:2456660] Person -[Person doesNotRecognizeSelector:] 2017-03-02 10:16:37.859 RunTimeDemo[35098:2456660] -[Person logUserName]: unrecognized selector sent to instance 0x600000003d40?
運(yùn)行結(jié)果正好驗(yàn)證了上述說(shuō)明, 最后找不到實(shí)現(xiàn)會(huì)調(diào)用doesNotRecognizeSelector方法
那如何做才能不讓其Crash呢?
做法是重寫(xiě)剛才說(shuō)的那幾個(gè)方法,那我們就倒著重寫(xiě)上面的方法。
3.重寫(xiě)方法來(lái)拯救Crash
1.在methodSignatureForSelector方法攔截 新建一個(gè)類(lèi)ForwardClass,代碼如下,具體看注釋
#import "Person.h"#define LOG_ClassAndFunctionName NSLog(@"%@ %s", [self class], __func__)@interface ForwardClass : NSObject- (void)logUserName;@end@implementation ForwardClass- (void)forwardLogUserName:(NSString *)userName; {LOG_ClassAndFunctionName;NSLog(@"userName = %@",userName); }@end@interface Person()@property (nonatomic, copy) NSString *userName; @property (nonatomic, strong) ForwardClass *forwardClass;@end@implementation Person- (instancetype)initWithUserName:(NSString *)userName {self = [super init];if (self) {_userName = [userName copy];_forwardClass = [[ForwardClass alloc] init];}return self; }+ (BOOL)resolveInstanceMethod:(SEL)sel {LOG_ClassAndFunctionName;return [super resolveInstanceMethod:sel]; }- (id)forwardingTargetForSelector:(SEL)aSelector {LOG_ClassAndFunctionName;return [super forwardingTargetForSelector:aSelector]; }- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {LOG_ClassAndFunctionName;NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];if (!signature) {/// 方法簽名為nil,就生成一個(gè)forwardClass的方法簽名并制定selectorsignature = [self.forwardClass methodSignatureForSelector:@selector(forwardLogUserName:)];}return signature; }- (void)forwardInvocation:(NSInvocation *)anInvocation {LOG_ClassAndFunctionName;/// 重定向方法 [anInvocation setSelector:@selector(forwardLogUserName:)];/// 假如調(diào)用的函數(shù)需要參數(shù)的話,在這里調(diào)用anInvocation的setArgument:atIndex:方法來(lái)進(jìn)行設(shè)置; 指定參數(shù),以指針?lè)绞?#xff0c;并且第一個(gè)參數(shù)的起始index是2,因?yàn)閕ndex為1,2的分別是self和selectorNSString *userName = self.userName;[anInvocation setArgument:&userName atIndex:2];/// ForwardClass 接受轉(zhuǎn)發(fā)的消息 [anInvocation invokeWithTarget:[[ForwardClass alloc] init]]; }- (void)doesNotRecognizeSelector:(SEL)aSelector {LOG_ClassAndFunctionName; }@end?
運(yùn)行結(jié)果:
2017-03-02 10:47:14.229 RunTimeDemo[36473:2479172] Person +[Person resolveInstanceMethod:] 2017-03-02 10:47:14.229 RunTimeDemo[36473:2479172] Person -[Person forwardingTargetForSelector:] 2017-03-02 10:47:14.229 RunTimeDemo[36473:2479172] Person -[Person methodSignatureForSelector:] 2017-03-02 10:47:14.230 RunTimeDemo[36473:2479172] Person +[Person resolveInstanceMethod:] 2017-03-02 10:47:14.230 RunTimeDemo[36473:2479172] Person -[Person forwardInvocation:] 2017-03-02 10:47:14.230 RunTimeDemo[36473:2479172] ForwardClass -[ForwardClass forwardLogUserName:] 2017-03-02 10:47:14.230 RunTimeDemo[36473:2479172] userName = 小王此時(shí)不Crash了,我們讓其執(zhí)行了forwardClass 中的forwardLogUserName方法。
2.在forwardingTargetForSelector處攔截,轉(zhuǎn)發(fā)給其他的類(lèi),執(zhí)行對(duì)應(yīng)的方法。代碼如下
#import "Person.h"#define LOG_ClassAndFunctionName NSLog(@"%@ %s", [self class], __func__)@interface ForwardClass : NSObject- (void)logUserName;@end@implementation ForwardClass- (void)forwardLogUserName:(NSString *)userName; {LOG_ClassAndFunctionName;NSLog(@"userName = %@",userName); }- (void)logUserName {LOG_ClassAndFunctionName; }@end@interface Person()@property (nonatomic, copy) NSString *userName; @property (nonatomic, strong) ForwardClass *forwardClass;@end@implementation Person- (instancetype)initWithUserName:(NSString *)userName {self = [super init];if (self) {_userName = [userName copy];_forwardClass = [[ForwardClass alloc] init];}return self; }+ (BOOL)resolveInstanceMethod:(SEL)sel {LOG_ClassAndFunctionName;return [super resolveInstanceMethod:sel]; }- (id)forwardingTargetForSelector:(SEL)aSelector {LOG_ClassAndFunctionName;id fowarding = [super forwardingTargetForSelector:aSelector];if (!fowarding) {/// 進(jìn)行攔截,讓ForwardClass的示例進(jìn)行執(zhí)行aSelector方法fowarding = [[ForwardClass alloc] init];}return fowarding; }- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {LOG_ClassAndFunctionName;NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];if (!signature) {/// 方法簽名為nil,就生成一個(gè)forwardClass的方法簽名并制定selectorsignature = [self.forwardClass methodSignatureForSelector:@selector(forwardLogUserName:)];}return signature; }- (void)forwardInvocation:(NSInvocation *)anInvocation {LOG_ClassAndFunctionName;/// 重定向方法 [anInvocation setSelector:@selector(forwardLogUserName:)];/// 假如調(diào)用的函數(shù)需要參數(shù)的話,在這里調(diào)用anInvocation的setArgument:atIndex:方法來(lái)進(jìn)行設(shè)置; 指定參數(shù),以指針?lè)绞?#xff0c;并且第一個(gè)參數(shù)的起始index是2,因?yàn)閕ndex為1,2的分別是self和selectorNSString *userName = self.userName;[anInvocation setArgument:&userName atIndex:2];/// ForwardClass 接受轉(zhuǎn)發(fā)的消息 [anInvocation invokeWithTarget:[[ForwardClass alloc] init]]; }- (void)doesNotRecognizeSelector:(SEL)aSelector {LOG_ClassAndFunctionName; }@end?
運(yùn)行結(jié)果為:
2017-03-02 12:35:02.033 RunTimeDemo[41433:2552894] Person +[Person resolveInstanceMethod:] 2017-03-02 12:35:02.033 RunTimeDemo[41433:2552894] Person -[Person forwardingTargetForSelector:] 2017-03-02 12:35:02.034 RunTimeDemo[41433:2552894] ForwardClass -[ForwardClass logUserName]3.在resolveInstanceMethod處攔截,動(dòng)態(tài)的為類(lèi)添加相應(yīng)的方法。代碼如下:
#import "Person.h" #import <objc/runtime.h>#define LOG_ClassAndFunctionName NSLog(@"%@ %s", [self class], __func__)@interface ForwardClass : NSObject- (void)logUserName;@end@implementation ForwardClass- (void)forwardLogUserName:(NSString *)userName; {LOG_ClassAndFunctionName;NSLog(@"userName = %@",userName); }- (void)logUserName {LOG_ClassAndFunctionName; }@end@interface Person()@property (nonatomic, copy) NSString *userName; @property (nonatomic, strong) ForwardClass *forwardClass;@end@implementation Personvoid dynamicMethodIMP(id self, SEL _cmd) {LOG_ClassAndFunctionName; }- (instancetype)initWithUserName:(NSString *)userName {self = [super init];if (self) {_userName = [userName copy];_forwardClass = [[ForwardClass alloc] init];}return self; }+ (BOOL)resolveInstanceMethod:(SEL)sel {LOG_ClassAndFunctionName;if (sel == @selector(logUserName)) {/// 動(dòng)態(tài)的為這個(gè)類(lèi)去添加方法class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");return YES;}return [super resolveInstanceMethod:sel]; }- (id)forwardingTargetForSelector:(SEL)aSelector {LOG_ClassAndFunctionName;id fowarding = [super forwardingTargetForSelector:aSelector];if (!fowarding) {/// 進(jìn)行攔截,讓ForwardClass的示例進(jìn)行執(zhí)行aSelector方法fowarding = [[ForwardClass alloc] init];}return fowarding; }- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {LOG_ClassAndFunctionName;NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];if (!signature) {/// 方法簽名為nil,就生成一個(gè)forwardClass的方法簽名并制定selectorsignature = [self.forwardClass methodSignatureForSelector:@selector(forwardLogUserName:)];}return signature; }- (void)forwardInvocation:(NSInvocation *)anInvocation {LOG_ClassAndFunctionName;/// 重定向方法 [anInvocation setSelector:@selector(forwardLogUserName:)];/// 假如調(diào)用的函數(shù)需要參數(shù)的話,在這里調(diào)用anInvocation的setArgument:atIndex:方法來(lái)進(jìn)行設(shè)置; 指定參數(shù),以指針?lè)绞?#xff0c;并且第一個(gè)參數(shù)的起始index是2,因?yàn)閕ndex為1,2的分別是self和selectorNSString *userName = self.userName;[anInvocation setArgument:&userName atIndex:2];/// ForwardClass 接受轉(zhuǎn)發(fā)的消息 [anInvocation invokeWithTarget:[[ForwardClass alloc] init]]; }- (void)doesNotRecognizeSelector:(SEL)aSelector {LOG_ClassAndFunctionName; }@end運(yùn)行結(jié)果為:
2017-03-02 12:44:24.529 RunTimeDemo[41577:2561853] Person +[Person resolveInstanceMethod:] 2017-03-02 12:44:24.530 RunTimeDemo[41577:2561853] Person dynamicMethodIMP總結(jié):上面三次攔截更加形象的說(shuō)明消息轉(zhuǎn)發(fā)進(jìn)行的次序。若某個(gè)類(lèi)的實(shí)例調(diào)用一個(gè)沒(méi)有實(shí)現(xiàn)的方法后。首先會(huì)調(diào)用
resolveInstanceMethod/resolveClassMethod方法,若沒(méi)有添加相應(yīng)的方法,其次就會(huì)調(diào)用forwardingTargetForSelector方法,若沒(méi)有轉(zhuǎn)發(fā)給其他對(duì)象,最后就調(diào)用methodSignatureForSelector方法,若方法簽名為nil,Runtime則會(huì)發(fā)出-doesNotRecognizeSelector:消息。此時(shí)就Crash了。
上面的工程鏈接為:?https://github.com/lidaojian/RunTimeDemo
?
================================================================
若有疑問(wèn)請(qǐng)加本人QQ:610774281 微信:stephenli225。 一起探討一起進(jìn)步。。。。
轉(zhuǎn)載于:https://www.cnblogs.com/lidaojian/p/6487110.html
總結(jié)
- 上一篇: 第九篇 并发(进程和线程)
- 下一篇: 夯实基础——P2084 进制转换