iOS消息機制


Objective-C Runtime

Describes the macOS Objective-C runtime library support functions and data structures.
 
Overview(概述)
 
以下是官方文檔中對Runtime給出的定義
The Objective-C runtime is a runtime library that provides support for the dynamic properties of the Objective-C language, and as such is linked to by all Objective-C apps.
正式runtime這一個庫給予了Objective-C language動態的屬性, 所有的OC App都可以直接使用它
 
You typically don't need to use the Objective-C runtime library directly when programming in Objective-C. This API is useful primarily for developing bridge layers between Objective-C and other languages, or for low-level debugging.
一般programming時不會直接使用到runtime庫, runtime這一功能或者屬性一般用在跨域語言編程, 或者在較底層的debug中

Note

 
All  char * in the runtime API should be considered to have UTF-8 encoding.
在runtime API中所有char類型都是以UTF-8編碼的
 
以上是文檔中對runtime做的一些簡單介紹
 
經過之前看過的其他人對runtime的經驗總結和自己的實踐, 目前對Runtime的概念:
 
消息動態解析
消息重定向
消息轉發
 
動態解析 在運行時(程序運行中)動態地:
給類中的已經定義但尚未實現的方法, 動態地綁定實現方法
給類增加或綁定既未定義也未實現方法, 說簡單就是給類增加方法
 
文檔中接下來是runtime方法的介紹, 我們在暫停在這里 先對上面幾個概念做一個簡單的說明
 
在之前必要我們先來看下[receiver message];這句話的實現過程, 也就是消息機制是如何在運作的
 
 1 struct objc_class {
 2     Class isa  OBJC_ISA_AVAILABILITY;
 3 #if !__OBJC2__
 4     Class super_class                                        OBJC2_UNAVAILABLE;
 5     const char *name                                         OBJC2_UNAVAILABLE;
 6     long version                                             OBJC2_UNAVAILABLE;
 7     long info                                                OBJC2_UNAVAILABLE;
 8     long instance_size                                       OBJC2_UNAVAILABLE;
 9     struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
10     struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
11     struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
12     struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
13 #endif
14 } OBJC2_UNAVAILABLE;
每一個NSObject對象都有成員變量列表, 方法列表, 緩存, 接口列表
方法列表中存儲方法的指針(IMP)
緩存中存儲的是曾經被調用的方法
 
[receiver message];會被轉換成消息發送的模式:
id objc_msgSend(id self, SEL _cmd, …);

 

 
當對象接收到消息時會按照以下順序依次檢查, 在任何一個環節如果被響應則結束 否則報錯
-> 對象接收到消息
-> 查看緩存中是否有匹配的方法, 如果有則響應 否則繼續
-> 查看方法列表中是否有匹配的方法, 如果有則響應 否則繼續
-> 查看父類中是否有匹配的方法, 如果有則響應 否則繼續
->進入動態解析 + (BOOL)resolveInstanceMethod:(SEL)sel, 如果有指定動態解析方法則響應 否則繼續
->進入消息重定向 - (id)forwardingTargetForSelector:(SEL)aSelector, 如果有指定消息接收對象則將消息轉由接收對象響應 否則繼續
->開始消息轉發 - (void)forwardInvocation:(NSInvocation *)anInvocation, 如果有指定轉發對象則轉發給該對象響應, 否則拋出異常
 
再消息轉發前我們有兩次機會來修改或者設定對象方法的實現
 
下面再逐一說說
動態解析
假如我們有一個ClassA, 在它的頭文件中我們定義了一個- (void)printName;方法, 但我們並沒有在.m文件中讓它實現
如果我們直接在Viewcontroller中使用[[ClassA new] printName];程序不會出錯 但也不會做任何事情
 我們可以重寫resolveInstanceMethod:或者resolveClassMethod:方法, 在這里我們給printName方法添加實現
 1 /**
 2  要動態綁定的方法
 3 
 4  @param self 要綁定方法的對象
 5  @param _cmd 方法信息
 6  */
 7 void dynamicMethodIMP(id self, SEL _cmd) {
 8     
 9     NSLog(@"SEL: %s method is added", sel_getName(_cmd));
10     NSLog(@"Name: Jackey");
11 }
12 
13 
14 /**
15  動態綁定和解析方法
16 
17  @param sel 方法信息
18  @return 是否已經處理該方法
19  */
20 + (BOOL)resolveInstanceMethod:(SEL)sel {
21     
22     NSLog(@"SEL: %s method does not exist", sel_getName(sel));
23     
24     if (sel == @selector(printName)) {
25         
26         class_addMethod ([self class], sel, (IMP) dynamicMethodIMP, "v@:");
27         return YES;
28     }
29     
30     return [super resolveInstanceMethod:sel];
31 }

這樣我們再運行[[ClassA new] printName];就會輸出Name: Jackey

 

重定向:

如果經過動態解析后, 消息還沒有被響應就會進入到重定向階段

我們可以重寫- (id)forwardingTargetForSelector:(SEL)aSelector將消息重定向給可以響應的對象

 1 /**
 2  方法重定向
 3 
 4  @param aSelector 方法信息
 5  @return 返回重定向后要相應的對象
 6  */
 7 - (id)forwardingTargetForSelector:(SEL)aSelector {
 8     
 9     NSLog(@"Current class can't response to SEL: %s", sel_getName(aSelector));
10     
11     if (aSelector == @selector(printRightName)) {
12         
13         NSLog(@"Forward to target: %@", [ClassB class]);
14         return [ClassB new];
15     }
16     
17     return [super forwardingTargetForSelector:aSelector];
18     
19 }

 

最后如果前面都沒有處理就會進入到消息轉發,  我們可以通過重寫

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector;

- (void)forwardInvocation:(NSInvocation *)anInvocation;

來自定義

 1 /**
 2  轉發前, 獲取方法簽名
 3 
 4  @param selector 方法信息
 5  @return NSInvocation消息對象
 6  */
 7 - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
 8     
 9     NSString *sel = NSStringFromSelector(selector);
10     if ([sel rangeOfString:@"set"].location == 0){
11         
12         return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
13     }
14     else{
15         
16         return [NSMethodSignature signatureWithObjCTypes:"@@:"];
17     }
18 }
19 
20 /**
21  轉發
22 
23  @param anInvocation 消息對象
24  */
25 - (void)forwardInvocation:(NSInvocation *)anInvocation {
26     
27     NSLog(@"No class can't response to SEL: %s", sel_getName([anInvocation selector]));
28     
29     ClassC *c = [ClassC new];
30     if ([c respondsToSelector:[anInvocation selector]]) {
31         
32         NSLog(@"method apply deliver to %@", [ClassC class]);
33         [anInvocation invokeWithTarget:c];
34     }
35     
36     else {
37         
38         [super forwardInvocation:anInvocation];
39     }
40 }

消息的轉發彌補了OC不能多繼承的問題

 

最后我們來看下Method Swizzling

我們可以直接修改方法的指針, 讓一個方法名指向其他的方法實現

1 Method ori_method = class_getInstanceMethod([ClassB class], @selector(printRightName));
2 Method my_method  = class_getInstanceMethod([ClassC class], @selector(printFamilyName));
3     
4 method_exchangeImplementations(ori_method, my_method);
5     
6 [[ClassB new] printRightName];

使用method_exchangeImplementation交換了兩個對象方法的指針

printRightName執行的實際是printFamilyName

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM