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 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類中實現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_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

?

運行結果為:

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

?

運行結果正好驗證了上述說明, 最后找不到實現會調用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:)];/// 假如調用的函數需要參數的話,在這里調用anInvocation的setArgument:atIndex:方法來進行設置; 指定參數,以指針方式,并且第一個參數的起始index是2,因為index為1,2的分別是self和selectorNSString *userName = self.userName;[anInvocation setArgument:&userName atIndex:2];/// ForwardClass 接受轉發的消息
    [anInvocation invokeWithTarget:[[ForwardClass alloc] init]];
}- (void)doesNotRecognizeSelector:(SEL)aSelector
{LOG_ClassAndFunctionName;
}@end

?

運行結果:

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了,我們讓其執行了forwardClass 中的forwardLogUserName方法。

2.在forwardingTargetForSelector處攔截,轉發給其他的類,執行對應的方法。代碼如下

#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的示例進行執行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:)];/// 假如調用的函數需要參數的話,在這里調用anInvocation的setArgument:atIndex:方法來進行設置; 指定參數,以指針方式,并且第一個參數的起始index是2,因為index為1,2的分別是self和selectorNSString *userName = self.userName;[anInvocation setArgument:&userName atIndex:2];/// ForwardClass 接受轉發的消息
    [anInvocation invokeWithTarget:[[ForwardClass alloc] init]];
}- (void)doesNotRecognizeSelector:(SEL)aSelector
{LOG_ClassAndFunctionName;
}@end

?

運行結果為:

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處攔截,動態的為類添加相應的方法。代碼如下:

#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)) {/// 動態的為這個類去添加方法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的示例進行執行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:)];/// 假如調用的函數需要參數的話,在這里調用anInvocation的setArgument:atIndex:方法來進行設置; 指定參數,以指針方式,并且第一個參數的起始index是2,因為index為1,2的分別是self和selectorNSString *userName = self.userName;[anInvocation setArgument:&userName atIndex:2];/// ForwardClass 接受轉發的消息
    [anInvocation invokeWithTarget:[[ForwardClass alloc] init]];
}- (void)doesNotRecognizeSelector:(SEL)aSelector
{LOG_ClassAndFunctionName;
}@end

運行結果為:

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

總結:上面三次攔截更加形象的說明消息轉發進行的次序。若某個類的實例調用一個沒有實現的方法后。首先會調用

resolveInstanceMethod/resolveClassMethod方法,若沒有添加相應的方法,其次就會調用forwardingTargetForSelector方法,若沒有轉發給其他對象,最后就調用methodSignatureForSelector方法,若方法簽名為nil,Runtime則會發出-doesNotRecognizeSelector:消息。此時就Crash了。

上面的工程鏈接為:?https://github.com/lidaojian/RunTimeDemo

?

================================================================

若有疑問請加本人QQ:610774281 微信:stephenli225。 一起探討一起進步。。。。

轉載于:https://www.cnblogs.com/lidaojian/p/6487110.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/386229.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/386229.shtml
英文地址,請注明出處:http://en.pswp.cn/news/386229.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

網易資深Java架構師:java數組對象轉為list集合

前言 現在刷抖音經常可以看到一些老外街坊&#xff0c;問他們最想把什么帶回自己的國家&#xff0c;我聽過很多的回答都是&#xff1a;淘寶&#xff0c;支付寶&#xff0c;美食&#xff0c;微信&#xff0c;外賣&#xff0c;高鐵等等。 確實如此&#xff0c;隨著國家的快速發…

夯實基礎——P2084 進制轉換

題目鏈接&#xff1a;https://www.luogu.org/problem/P2084 P2084 進制轉換 題目背景 無 題目描述 今天小明學會了進制轉換&#xff0c;比如&#xff08;10101&#xff09;2 &#xff0c;那么它的十進制表示的式子就是 : 1*2^40*2^31*2^20*2^11*2^0&#xff0c; 那么請你編程實…

網易資深Java架構師:java方法的定義和使用

前言 今年因為這個疫情&#xff0c;感覺這是從工作以來過的最久的一個年了&#xff0c;在家呆的時間不是一般的久&#xff0c;算一算有好幾個月呢&#xff01;我大概是3月底快4月了才出門&#xff0c;投了超多的簡歷&#xff0c;天天面試面試面試面試面試面試面試…慶幸的是還…

PHP----學生管理系統

閑來無事花費兩天時間寫了份簡易版的學生管理系統 源碼地址:https://www.cnblogs.com/post/ReadAuth?blogId509327&PostId11333758&url%2Fbyczyz%2Fprotected%2Fp%2F11333758.html 轉載于:https://www.cnblogs.com/byczyz/p/11333760.html

網易資深Java架構師:jdkjrejvm的區別和聯系

前言 作為同時具備高性能、高可靠和高可擴展性的典型鍵值數據庫&#xff0c;Redis不僅功能強大&#xff0c;而且穩定&#xff0c;理所當然地成為了大型互聯網公司的首選。 眾多大廠在招聘的時候&#xff0c;不僅會要求面試者能簡單地使用Redis&#xff0c;還要能深入地理解底…

深度學習之開端備注

Adagrad //適合稀疏樣本 RMSprop//借鑒Adagrad的思想&#xff0c;改進使得不會出現學習率越來越低的問題 由此可見Adadelta既不需要輸入學習率等參數&#xff0c;而且表現得非常好&#xff01;&#xff01;但是我試了幾次&#xff0c;這個優化器效果極差&#xff01;&#xff0…

網易資深Java架構師:疫情對java行業的影響分析

前言 在實際開發&#xff0c;Redis使用會頻繁&#xff0c;那么在使用過程中我們該如何正確抉擇數據類型呢&#xff1f;哪些場景下適用哪些數據類型。而且在面試中也很常會被面試官問到Redis數據結構方面的問題&#xff1a; Redis為什么快呢&#xff1f;為什么查詢操作會變慢了…

ListView與.FindControl()方法的簡單練習 #2 -- ItemUpdting事件中抓取「修改后」的值

原文出處 http://www.dotblogs.com.tw/mis2000lab/archive/2013/06/24/listview_itemupdating_findcontrol_20130624.aspx ListView與.FindControl()方法的簡單練習 #2 -- ItemUpdting事件中抓取「修改后」的值 本文跟上一篇文章有關連&#xff0c;請依照順序來練習&#xff1…

美團java研發崗二面:java靜態方法存儲在哪個區

思維導圖 前言 在很多時候&#xff0c;我們都可以在各種框架應用中看到ZooKeeper的身影&#xff0c;比如Kafka中間件&#xff0c;Dubbo框架&#xff0c;Hadoop等等。為什么到處都看到ZooKeeper&#xff1f; 一、 前些年&#xff0c;互聯網行業里對架構師這個崗位的標準還不是…

[學習之道] 修福不修慧,大象披瓔珞; 修慧不修福,羅漢托空缽 (學習寫程序,只靠補習上課嗎?)...

這是我的備份&#xff0c;原文請看 http://www.dotblogs.com.tw/mis2000lab/archive/2014/09/17/learning-and_do-it_20140917.aspx [學習之道] 修福不修慧&#xff0c;大象披瓔珞&#xff1b; 修慧不修福&#xff0c;羅漢托空缽 (學習寫程序&#xff0c;只靠補習上課嗎&#…

阿里P8親自教你!mysql列轉行

前言 今日博主聽聞&#xff0c;現在很多培訓出來的應屆生薪資都趕上了摸爬滾打兩三年的朋友&#xff0c;講道理&#xff0c;這說不過去啊 作為同行來說&#xff0c;這個行業發展很快&#xff0c;技術更新很快&#xff0c;淘汰也很快&#xff0c;千萬不要再找借口了&#xff0…

同步、異步、多線程

1、首先明確一點&#xff0c;對于單核CPU&#xff0c;任意一個時刻只有一個線程在運行。那么既然這樣&#xff0c;多線程還有什么意義呢&#xff1f; 舉例來說&#xff0c;現在只有一個人&#xff0c;要做好幾個任務。單線程就是&#xff0c;任務一個一個地做&#xff0c;必須做…

阿里P8親自教你!熬夜整理華為最新Java筆試題

前言 Mysql的鎖機制確實非常重要&#xff0c;所以在這里做一個全面的總結整理&#xff0c;便于以后的查閱&#xff0c;也分享給大家。 Mysql的鎖機制還是有點難理解的&#xff0c;所以這篇文章采用圖文結合的方式講解難點&#xff0c;幫助大家理解&#xff0c;講解的主要內容…

JSP基礎筆記

/** #####這部分也是筆記,用于記錄JSP的相關內容* ###怎么用JSP###指令的寫法* <% 指令名字%>* *### page指令 * language > 表明jsp頁面中可以寫java代碼 * contentType > 其實即使說這個文件是什么類型&#xff0c;告訴瀏覽器我是什么內容類型&#xff0c;以及使…

阿里P8親自講解!javawhile循環語句用法

前言 作為一個已經畢業的計算機專業學長&#xff0c;其實幾年大學走來還是挺感慨萬千的。&#xff08;說明一下&#xff1a;一本&#xff0c;非958、211&#xff09; 老實說&#xff0c;上大學之前填志愿選專業的時候沒有任何打算&#xff0c;就覺得學海熬到頭了&#xff0c;向…

Cookie,Session基礎知識

//這部分主要是CookieSession的筆記部分/** //獲取來訪的客戶端類型String clientTyereq.getHeader("User-Agent");//如果是火狐瀏覽器&#xff0c;那么使用以下代碼if(clientTye.contains("Firefox")){fileNameDownLoadUtil.base64EncodeFileName(fileNam…

阿里P8親自講解!java中級開發工程師需要掌握的技能

前言 關于技術人如何成長的問題&#xff0c;一直以來都備受關注&#xff0c;因為程序員職業發展很快&#xff0c;即使是相同起點的人&#xff0c;經過幾年的工作或學習&#xff0c;會迅速拉開極大的差距&#xff0c;所以技術人保持學習&#xff0c;提升自己&#xff0c;才能夠…

隨筆--互聯網進化論

不聞不若聞之&#xff1b;有的人士的理論引起了轟動&#xff0c;吾等小網民也來學學&#xff0c;沒那么大腦袋&#xff0c;從不敢談解讀與批判。聞香而來&#xff0c;放屁而去。比如說互聯網進化論。看看實踐&#xff0c;互聯網的膨脹式發展是不會停的&#xff0c;在中國的互聯…

阿里P8親自講解!java分布式需要學什么技術

引言 最近項目上線的頻率頗高&#xff0c;連著幾天加班熬夜&#xff0c;身體有點吃不消精神也有些萎靡&#xff0c;無奈業務方催的緊&#xff0c;工期就在眼前只能硬著頭皮上了。腦子渾渾噩噩的時候&#xff0c;寫的就不能叫代碼&#xff0c;可以直接叫做Bug。我就熬夜寫了一個…

Javascript的this用法

出自&#xff1a;http://www.ruanyifeng.com/blog/2010/04/using_this_keyword_in_javascript.html this是Javascript語言的一個關鍵字。 它代表函數運行時&#xff0c;自動生成的一個內部對象&#xff0c;只能在函數內部使用。比如&#xff0c; function test(){ this.x 1; }…