日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

IOS 消息转发

發布時間:2023/11/30 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 IOS 消息转发 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

最近在看消息轉發的資料,發現大部分都是理論知識,很少有完整的代碼?,F在以代碼的形式形象的解釋一下:

用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

總結

以上是生活随笔為你收集整理的IOS 消息转发的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。