IOS 消息转发
最近在看消息轉(zhuǎn)發(fā)的資料,發(fā)現(xiàn)大部分都是理論知識,很少有完整的代碼。現(xiàn)在以代碼的形式形象的解釋一下:
用Xcode創(chuàng)建一個工程
1.正常方法調(diào)用
創(chuàng)建一個類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運行工程結(jié)果為:
2017-03-01 22:47:07.296 RunTimeDemo[24364:1776826] userName = 小王結(jié)果正常
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運行工程會出現(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類中實現(xiàn)logUsrName這個方法,也可以讓工程正常運行,此時就涉及到消息轉(zhuǎn)發(fā),Objective-C運行時會給出三次拯救程序崩潰的機會。
如下:
(1).Method resolution
objc運行時會調(diào)用+resolveInstanceMethod:或者 +resolveClassMethod:,讓你有機會提供一個函數(shù)實現(xiàn)。如果你添加了函數(shù),那運行時系統(tǒng)就會重新啟動一次消息發(fā)送的過程,否則 ,運行時就會移到下一步,消息轉(zhuǎn)發(fā)(Message Forwarding)。
(2).Fast forwarding
如果目標對象實現(xiàn)了-forwardingTargetForSelector:,Runtime 這時就會調(diào)用這個方法,給你把這個消息轉(zhuǎn)發(fā)給其他對象的機會。 只要這個方法返回的不是nil和self,整個消息發(fā)送的過程就會被重啟,當然發(fā)送的對象會變成你返回的那個對象。否則,就會繼續(xù)Normal Fowarding。 這里叫Fast,只是為了區(qū)別下一步的轉(zhuǎn)發(fā)機制。因為這一步不會創(chuàng)建任何新的對象,但下一步轉(zhuǎn)發(fā)會創(chuàng)建一個NSInvocation對象,所以相對更快點。
(3).Normal forwarding
這一步是Runtime最后一次給你挽救的機會。首先它會發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型。如果-methodSignatureForSelector:返回nil,Runtime則會發(fā)出-doesNotRecognizeSelector:消息,程序這時也就掛掉了。如果返回了一個函數(shù)簽名,Runtime就會創(chuàng)建一個NSInvocation對象并發(fā)送-forwardInvocation:消息給目標對象。
?
下面就驗證一下上面所對應(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?
運行結(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?
運行結(jié)果正好驗證了上述說明, 最后找不到實現(xiàn)會調(diào)用doesNotRecognizeSelector方法
那如何做才能不讓其Crash呢?
做法是重寫剛才說的那幾個方法,那我們就倒著重寫上面的方法。
3.重寫方法來拯救Crash
1.在methodSignatureForSelector方法攔截 新建一個類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,就生成一個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:方法來進行設(shè)置; 指定參數(shù),以指針方式,并且第一個參數(shù)的起始index是2,因為index為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?
運行結(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 = 小王此時不Crash了,我們讓其執(zhí)行了forwardClass 中的forwardLogUserName方法。
2.在forwardingTargetForSelector處攔截,轉(zhuǎn)發(fā)給其他的類,執(zhí)行對應(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) {/// 進行攔截,讓ForwardClass的示例進行執(zhí)行aSelector方法fowarding = [[ForwardClass alloc] init];}return fowarding; }- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {LOG_ClassAndFunctionName;NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];if (!signature) {/// 方法簽名為nil,就生成一個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:方法來進行設(shè)置; 指定參數(shù),以指針方式,并且第一個參數(shù)的起始index是2,因為index為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?
運行結(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處攔截,動態(tà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)) {/// 動態(tà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) {/// 進行攔截,讓ForwardClass的示例進行執(zhí)行aSelector方法fowarding = [[ForwardClass alloc] init];}return fowarding; }- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {LOG_ClassAndFunctionName;NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];if (!signature) {/// 方法簽名為nil,就生成一個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:方法來進行設(shè)置; 指定參數(shù),以指針方式,并且第一個參數(shù)的起始index是2,因為index為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運行結(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é):上面三次攔截更加形象的說明消息轉(zhuǎn)發(fā)進行的次序。若某個類的實例調(diào)用一個沒有實現(xiàn)的方法后。首先會調(diào)用
resolveInstanceMethod/resolveClassMethod方法,若沒有添加相應(yīng)的方法,其次就會調(diào)用forwardingTargetForSelector方法,若沒有轉(zhuǎn)發(fā)給其他對象,最后就調(diào)用methodSignatureForSelector方法,若方法簽名為nil,Runtime則會發(fā)出-doesNotRecognizeSelector:消息。此時就Crash了。
上面的工程鏈接為:?https://github.com/lidaojian/RunTimeDemo
?
================================================================
若有疑問請加本人QQ:610774281 微信:stephenli225。 一起探討一起進步。。。。
轉(zhuǎn)載于:https://www.cnblogs.com/lidaojian/p/6487110.html
總結(jié)
- 上一篇: 第九篇 并发(进程和线程)
- 下一篇: 不孕不育要做什么检查什么项目?