[轉]runtime 詳解

公司項目用到一個三方開源庫,裏面有個bug,不能改動源碼,我想來想去,只能經過runtime這個萬能的手段來解決。可是runtime 並不怎麼會用,怎麼辦,立刻學習唄。說到runtime,它是Objective-c裏面最核心的技術,被人們傳呼的神乎其神,可是感受有一層神祕的面紗籠罩其上,畢竟使用場景很少,相信大多數開發者都不會熟練的運用。而網絡上也有無數的文章來說解runtime,可是真的很是的亂,很是的碎片化,不多有講解的比較全面的。
 
最初是在onevcat的博客上看到runtime的runtime的博客,說句實話,看完後我仍是蒙的,這裏面主要講了一下runtime 比較核心的功能-Method Swizzling,不過看完後仍是有些不知如何下手的感受。下面是我本身對runtime的整理,從零開始,由淺入深,而且帶了幾個runtime實際的應用場景。看完以後,你能夠再回過頭來看喵神的這篇文章,應該就能看的懂了。
 
一:基本概念
Runtime基本是用C和彙編寫的,可見蘋果爲了動態系統的高效而做出的努力。你能夠在這裏下到蘋果維護的開源代碼。蘋果和GNU各自維護一個開源的runtime版本,這兩個版本之間都在努力的保持一致。Objective-C 從三種不一樣的層級上與 Runtime 系統進行交互,分別是經過 Objective-C 源代碼,經過 Foundation 框架的NSObject類定義的方法,經過對 runtime 函數的直接調用。大部分狀況下你就只管寫你的Objc代碼就行,runtime 系統自動在幕後辛勤勞做着。
 
  • RunTime簡稱運行時,就是系統在運行的時候的一些機制,其中最主要的是消息機制。
  • 對於C語言,函數的調用在編譯的時候會決定調用哪一個函數,編譯完成以後直接順序執行,無任何二義性。
  • OC的函數調用成爲消息發送。屬於動態調用過程。在編譯的時候並不能決定真正調用哪一個函數(事實證實,在編 譯階段,OC能夠調用任何函數,即便這個函數並未實現,只要申明過就不會報錯。而C語言在編譯階段就會報錯)。
  • 只有在真正運行的時候纔會根據函數的名稱找 到對應的函數來調用。
 
二:runtime的具體實現
咱們寫的oc代碼,它在運行的時候也是轉換成了runtime方式運行的,更好的理解runtime,也能幫咱們更深的掌握oc語言。
每個oc的方法,底層必然有一個與之對應的runtime方法。
<ignore_js_op>
 
  • 當咱們用OC寫下這樣一段代碼
    [tableView cellForRowAtIndexPath:indexPath];
  • 在編譯時RunTime會將上述代碼轉化成[發送消息]
    objc_msgSend(tableView, @selector(cellForRowAtIndexPath,indexPath);

 

三:常見方法
unsigned int count;
  • 獲取屬性列表
[Objective-C]  查看源文件 複製代碼
?
1
2
3
4
5
objc_property_t *propertyList = class_copyPropertyList([ self class ], &count);
     for (unsigned int i=0; i<count; i++) {
         const char *propertyName = property_getName(propertyList[i]);[/i]
[i]        NSLog ( @"property---->%@" , [ NSString stringWithUTF8String:propertyName]);[/i]
[i]    }



[color=rgba(0, 0, 0, 0.6)]獲取方法列表html

[Objective-C]  查看源文件 複製代碼
?
1
2
3
4
5
ethod *methodList = class_copyMethodList([ self class ], &count);
for (unsigned int i; i<count; i++) {
     Method method = methodList[i];
     NSLog ( @"method---->%@" , NSStringFromSelector (method_getName(method)));
}
獲取成員變量列表
[Objective-C]  查看源文件 複製代碼
?
1
2
3
4
5
6
Ivar *ivarList = class_copyIvarList([ self class ], &count);
   for (unsigned int i; i<count; i++) {
       Ivar myIvar = ivarList[i];
       const char *ivarName = ivar_getName(myIvar);
       NSLog ( @"Ivar---->%@" , [ NSString stringWithUTF8String:ivarName]);
   }
獲取協議列表
[Objective-C]  查看源文件 複製代碼
?
1
2
3
4
5
6
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([ self class ], &count);
     for (unsigned int i; i<count; i++) {
         Protocol *myProtocal = protocolList[i];
         const char *protocolName = protocol_getName(myProtocal);
         NSLog ( @"protocol---->%@" , [ NSString stringWithUTF8String:protocolName]);
     }
>如今有一個Person類,和person建立的xiaoming對象,有test1和test2兩個方法
 
得到類方法
[Objective-C]  查看源文件 複製代碼
?
1
2
3
Class PersonClass = object_getClass([Person class ]);
SEL oriSEL = @selector (test1);
Method oriMethod = class_getInstanceMethod(xiaomingClass, oriSEL);
得到實例方法
[Objective-C]  查看源文件 複製代碼
?
1
2
3
Class PersonClass = object_getClass([xiaoming class ]);
SEL oriSEL = @selector (test2);
Method cusMethod = class_getInstanceMethod(xiaomingClass, oriSEL);


添加方法
[Objective-C]  查看源文件 複製代碼
?
1
BOOL addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));


替換原方法實現
[Objective-C]  查看源文件 複製代碼
?
1
class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));


交換兩個方法
[Objective-C]  查看源文件 複製代碼
?
1
method_exchangeImplementations(oriMethod, cusMethod);

 

四:常見做用
- 動態的添加對象的成員變量和方法
- 動態交換兩個方法的實現
- 攔截並替換方法
- 在方法上增長額外功能
- 實現NSCoding的自動歸檔和解檔
- 實現字典轉模型的自動轉換

 

五:代碼實現
要使用runtime,要先引入頭文件`#import <objc/runtime.h>`
這些代碼的實例有淺入深逐步講解,最後附上一個我在公司項目中遇到的一個實際問題。

 

1. 動態變量控制
   在程序中,xiaoming的age是10,後來被runtime變成了20,來看看runtime是怎麼作到的。

 

1.動態獲取XiaoMing類中的全部屬性[固然包括私有]  

 

        `Ivar *ivar = class_copyIvarList([self.xiaoming class], &count);`  

 

2.遍歷屬性找到對應name字段  

 

        `const char *varName = ivar_getName(var);`

 

3.修改對應的字段值成20

 

        `object_setIvar(self.xiaoMing, var, @"20");`  

 

4.代碼參考
[Objective-C]  查看源文件 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
-( void )answer{
unsigned int count = 0;
Ivar ivar = class_copyIvarList([ self .xiaoMing class ], &count);
for ( int i = 0; i<count; i++) {
Ivar var = ivar[i];
const char varName = ivar_getName(var);
NSString *name = [ NSString stringWithUTF8String:varName];
if ([name isEqualToString:@」_age」]) {
object_setIvar( self .xiaoMing, var, @」20」);
break ;
}
}
NSLog (@」XiaoMing’s age is %@」, self .xiaoMing.age);
}


2.動態添加方法ios

在程序當中,假設XiaoMing的中沒有guess這個方法,後來被Runtime添加一個名字叫guess的方法,最終再調用guess方法作出相應。那麼,Runtime是如何作到的呢?
1.動態給XiaoMing類中添加guess方法:
[Objective-C]  查看源文件 複製代碼
?
1
class_addMethod([ self .xiaoMing class ], @selector (guess), (IMP)guessAnswer, "v@: " );


這裏參數地方說明一下:
[Objective-C]  查看源文件 複製代碼
?
1
2
3
(IMP)guessAnswer 意思是guessAnswer的地址指針;
"v@:" 意思是,v表明無返回值 void ,若是是i則表明 int ;@表明 id sel; : 表明 SEL _cmd;
「v@:@@」 意思是,兩個參數的沒有返回值。



2.調用guess方法響應事件:  
[self.xiaoMing performSelectorselector(guess)];

3.編寫guessAnswer的實現:  
void guessAnswer(id self,SEL _cmd){
     NSLog(@"i am from beijing");

這個有兩個地方留意一下:objective-c

  • void的前面沒有+、-號,由於只是C的代碼。
  • 必須有兩個指定參數(id self,SEL _cmd)


4.代碼參考數組

[Objective-C]  查看源文件 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
-( void )[i]answer{[/i]
class_addMethod([ self .xiaoMing class ], @selector (guess), (IMP)guessAnswer, "v@:" );
      if ([ self .xiaoMing respondsToSelector: @selector (guess)]) {
 
          [ self .xiaoMing performSelector: @selector (guess)];
 
      } else {
          NSLog ( @"Sorry,I don't know" );
      }
  }
 
  void guessAnswer( id self , SEL _cmd){
 
      NSLog ( @"i am from beijing" );
 
  }


3:動態交換兩個方法的實現
在程序當中,假設XiaoMing的中有test1 和 test2這兩個方法,後來被Runtime交換方法後,每次調動test1 的時候就會去執行test2,調動test2 的時候就會去執行test1, 。那麼,Runtime是如何作到的呢?緩存

  • 獲取這個類中的兩個方法並交換
[Objective-C]  查看源文件 複製代碼
?
1
2
3
Method m1 = class_getInstanceMethod([ self .xiaoMing class ], @selector (test1));
     Method m2 = class_getInstanceMethod([ self .xiaoMing class ], @selector (test2));
     method_exchangeImplementations(m1, m2);
交換方法以後,之後每次調用這兩個方法都會交換方法的實現
 

4:攔截並替換方法
在程序當中,假設XiaoMing的中有test1這個方法,可是因爲某種緣由,咱們要改變這個方法的實現,可是又不能去動它的源代碼(正如一些開源庫出現問題的時候),這個時候runtime就派上用場了。
咱們先增長一個tool類,而後寫一個咱們本身實現的方法-change,
經過runtime把test1替換成change。網絡


[Objective-C]  查看源文件 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Class PersionClass = object_getClass([Person class ]);
Class toolClass = object_getClass([tool class ]);
 
     ////源方法的SEL和Method
 
     SEL oriSEL = @selector (test1);
     Method oriMethod = class_getInstanceMethod(PersionClass, oriSEL);
 
     ////交換方法的SEL和Method
 
     SEL cusSEL = @selector (change);
     Method cusMethod = class_getInstanceMethod(toolClass, cusSEL);
 
     ////先嚐試給源方法添加實現,這裏是爲了不源方法沒有實現的狀況
 
     BOOL addSucc = class_addMethod(PersionClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
     if (addSucc) {
           // 添加成功:將源方法的實現替換到交換方法的實現    
         class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
 
     } else {
     //添加失敗:說明源方法已經有實現,直接將兩個方法的實現交換即
method_exchangeImplementations(oriMethod, cusMethod); 
   }
 

5:在方法上增長額外功能
有這樣一個場景,出於某些需求,咱們須要跟蹤記錄APP中按鈕的點擊次數和頻率等數據,怎麼解決?固然經過繼承按鈕類或者經過類別實現是一個辦法,可是帶來其餘問題好比別人不必定會去實例化你寫的子類,或者其餘類別也實現了點擊方法致使不肯定會調用哪個,runtime能夠這樣解決:數據結構


[Objective-C]  查看源文件 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@implementation UIButton (Hook)
 
+ ( void )load {
 
     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
 
         Class selfClass = [ self class ];
 
         SEL oriSEL = @selector (sendAction:to:forEvent:);
         Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);
 
         SEL cusSEL = @selector (mySendAction:to:forEvent:);
         Method cusMethod = class_getInstanceMethod(selfClass, cusSEL);
 
         BOOL addSucc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
         if (addSucc) {
             class_replaceMethod(selfClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
         } else {
             method_exchangeImplementations(oriMethod, cusMethod);
         }
 
     });
}
 
- ( void )mySendAction:( SEL )action to:( id )target forEvent:(UIEvent *)event {
     [CountTool addClickCount];
     [ self mySendAction:action to:target forEvent:event];
}
 
@end
load方法會在類第一次加載的時候被調用,調用的時間比較靠前,適合在這個方法裏作方法交換,方法交換應該被保證,在程序中只會執行一次。

6.實現NSCoding的自動歸檔和解檔
若是你實現過自定義模型數據持久化的過程,那麼你也確定明白,若是一個模型有許多個屬性,那麼咱們須要對每一個屬性都實現一遍encodeObject 和 decodeObjectForKey方法,若是這樣的模型又有不少個,這還真的是一個十分麻煩的事情。下面來看看簡單的實現方式。
假設如今有一個Movie類,有3個屬性,它的h文件這這樣的app


[Objective-C]  查看源文件 複製代碼
?
01
02
03
04
05
06
07
08
09
10
#import <Foundation/Foundation.h>
 
//1. 若是想要當前類能夠實現歸檔與反歸檔,須要遵照一個協議NSCoding
@interface Movie : NSObject < NSCoding >
 
@property ( nonatomic , copy ) NSString *movieId;
@property ( nonatomic , copy ) NSString *movieName;
@property ( nonatomic , copy ) NSString *pic_url;
 
@end


若是是正常寫法, m文件應該是這樣的:

[Objective-C]  查看源文件 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
#import "Movie.h"
@implementation Movie
 
- ( void )encodeWithCoder:( NSCoder *)aCoder
{
     [aCoder encodeObject:_movieId forKey: @"id" ];
     [aCoder encodeObject:_movieName forKey: @"name" ];
     [aCoder encodeObject:_pic_url forKey: @"url" ];
 
}
 
- ( id )initWithCoder:( NSCoder *)aDecoder
{
     if ( self = [ super init]) {
         self .movieId = [aDecoder decodeObjectForKey: @"id" ];
         self .movieName = [aDecoder decodeObjectForKey: @"name" ];
         self .pic_url = [aDecoder decodeObjectForKey: @"url" ];
     }
     return self ;
}
@end


若是這裏有100個屬性,那麼咱們也只能把100個屬性都給寫一遍。
不過你會使用runtime後,這裏就有更簡便的方法。
下面看看runtime的實現方式:
[Objective-C]  查看源文件 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#import "Movie.h"
#import <objc/runtime.h>
@implementation Movie
 
- ( void )encodeWithCoder:( NSCoder *)encoder
 
{
     unsigned int count = 0;
     Ivar *ivars = class_copyIvarList([Movie class ], &count);
 
     for ( int i = 0; i<count; i++) {
         // 取出i位置對應的成員變量
         Ivar ivar = ivars;
         // 查當作員變量
         const char *name = ivar_getName(ivar);
         // 歸檔
         NSString *key = [ NSString stringWithUTF8String:name];
         id value = [ self valueForKey:key];
         [encoder encodeObject:value forKey:key];
     }
     free(ivars);
}
 
- ( id )initWithCoder:( NSCoder *)decoder
{
     if ( self = [ super init]) {
         unsigned int count = 0;
         Ivar *ivars = class_copyIvarList([Movie class ], &count);
         for ( int i = 0; i<count; i++) {
         // 取出i位置對應的成員變量
         Ivar ivar = ivars;
         // 查當作員變量
         const char *name = ivar_getName(ivar);
        // 歸檔
        NSString *key = [ NSString stringWithUTF8String:name];
       id value = [decoder decodeObjectForKey:key];
        // 設置到成員變量身上
         [ self setValue:value forKey:key];
 
         }
         free(ivars);
     }
     return self ;
}
@end

這樣的方式實現,無論有多少個屬性,寫這幾行代碼就搞定了。怎麼,還嫌麻煩,下面看看更加簡便的方法:兩句代碼搞定。
咱們把encodeWithCoder 和 initWithCoder這兩個方法抽成宏

[Objective-C]  查看源文件 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#import "Movie.h"
#import <objc/runtime.h>
 
#define encodeRuntime(A) \
\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([A class ], &count);\
for ( int i = 0; i<count; i++) {\
Ivar ivar = ivars;\
const char *name = ivar_getName(ivar);\
NSString *key = [ NSString stringWithUTF8String:name];\
id value = [ self valueForKey:key];\
[encoder encodeObject:value forKey:key];\
}\
free(ivars);\
\
 
#define initCoderRuntime(A) \
\
if ( self = [ super init]) {\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([A class ], &count);\
for ( int i = 0; i<count; i++) {\
Ivar ivar = ivars;\
const char *name = ivar_getName(ivar);\
NSString *key = [ NSString stringWithUTF8String:name];\
id value = [decoder decodeObjectForKey:key];\
[ self setValue:value forKey:key];\
}\
free(ivars);\
}\
return self ;\
\
 
@implementation Movie
 
- ( void )encodeWithCoder:( NSCoder *)encoder
 
{
     encodeRuntime(Movie)
}
 
- ( id )initWithCoder:( NSCoder *)decoder
{
     initCoderRuntime(Movie)
}
@end


咱們能夠把這兩個宏單獨放到一個文件裏面,這裏之後須要進行數據持久化的模型均可以直接使用這兩個宏。

7.實現字典轉模型的自動轉換
字典轉模型的應用能夠說是每一個app必然會使用的場景,雖然實現的方式略有不一樣,可是原理都是一致的:遍歷模型中全部屬性,根據模型的屬性名,去字典中查找key,取出對應的值,給模型的屬性賦值。
像幾個出名的開源庫:JSONModel,MJExtension等都是經過這種方式實現的。
框架

  • 先實現最外層的屬性轉換

[Objective-C]  查看源文件 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 建立對應模型對象
     id objc = [[ self alloc] init];
 
     unsigned int count = 0;
 
     // 1.獲取成員屬性數組
     Ivar *ivarList = class_copyIvarList( self , &count);
 
     // 2.遍歷全部的成員屬性名,一個一個去字典中取出對應的value給模型屬性賦值
     for ( int i = 0; i < count; i++) {
 
         // 2.1 獲取成員屬性
         Ivar ivar = ivarList;
 
         // 2.2 獲取成員屬性名 C -> OC 字符串
        NSString *ivarName = [ NSString stringWithUTF8String:ivar_getName(ivar)];
 
         // 2.3 _成員屬性名 => 字典key
         NSString *key = [ivarName substringFromIndex:1];
 
         // 2.4 去字典中取出對應value給模型屬性賦值
         id value = dict[key];
 
         // 獲取成員屬性類型
         NSString *ivarType = [ NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
         }


若是模型比較簡單,只有NSString,NSNumber等,這樣就能夠搞定了。可是若是模型含有NSArray,或者NSDictionary等,那麼咱們還須要進行第二步轉換。
  • 內層數組,字典的轉換

    [Objective-C]  查看源文件 複製代碼
    ?
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    if ([value isKindOfClass:[ NSDictionary class ]] && ![ivarType containsString: @"NS" ]) {
     
                  //  是字典對象,而且屬性名對應類型是自定義類型
                 // 處理類型字符串 @\"User\" -> User
                 ivarType = [ivarType stringByReplacingOccurrencesOfString: @"@" withString: @"" ];
                 ivarType = [ivarType stringByReplacingOccurrencesOfString: @"\"" withString: @"" ];
                 // 自定義對象,而且值是字典
                 // value:user字典 -> User模型
                 // 獲取模型(user)類對象
                 Class modalClass = NSClassFromString (ivarType);
     
                 // 字典轉模型
                 if (modalClass) {
                     // 字典轉模型 user
                     value = [modalClass objectWithDict:value];
                 }
     
             }
     
             if ([value isKindOfClass:[ NSArray class ]]) {
                 // 判斷對應類有沒有實現字典數組轉模型數組的協議
                 if ([ self respondsToSelector: @selector (arrayContainModelClass)]) {
     
                     // 轉換成id類型,就能調用任何對象的方法
                     id idSelf = self ;
     
                     // 獲取數組中字典對應的模型
                     NSString *type =  [idSelf arrayContainModelClass][key];
     
                     // 生成模型
                     Class classModel = NSClassFromString (type);
                     NSMutableArray *arrM = [ NSMutableArray array];
                     // 遍歷字典數組,生成模型數組
                     for ( NSDictionary *dict in value) {
                         // 字典轉模型
                         id model =  [classModel objectWithDict:dict];
                         [arrM addObject:model];
                     }
     
                     // 把模型數組賦值給value
                     value = arrM;
     
                 }
             }

我本身以爲系統自帶的KVC模式字典轉模型就挺好的,假設movie是一個模型對象,dict 是一個須要轉化的 [movie setValuesForKeysWithDictionary:dict]; 這個是系統自帶的字典轉模型方法,我的感受也仍是挺好用的,不過使用這個方法的時候須要在模型裏面再實現一個方法才行,
- (void)setValue: (id)value forUndefinedKey: (NSString *)key 重寫這個方法爲了實現兩個目的:1. 模型中的屬性和字典中的key不一致的狀況,好比字典中有個id,咱們須要把它賦值給uid屬性;2. 字典中屬性比模型的屬性還多的狀況。
若是出現以上兩種狀況而沒有實現這個方法的話,程序就會崩潰。
這個方法的實現:


函數

[Objective-C]  查看源文件 複製代碼
?
1
2
3
4
5
6
- ( void )setValue:( id )value forUndefinedKey:( NSString *)key
{
     if ([key isEqualToString: @"id" ]) {
         self .uid = value;
     }
}




六.幾個參數概念

以上的幾種方法應該算是runtime在實際場景中所應用的大部分的狀況了,日常的編碼中差很少足夠用了。
若是從頭仔細看到尾,相信你基本的用法應該會了,雖然會用是主要的目的,有幾個基本的參數概念仍是要了解一下的。

1.objc_msgSend

[Objective-C]  查看源文件 複製代碼
?
1
2
3
4
5
6
7
8
9
/* Basic Messaging Primitives
*
* On some architectures, use objc_msgSend_stret for some struct return types.
* On some architectures, use objc_msgSend_fpret for some float return types.
* On some architectures, use objc_msgSend_fp2ret for some float return types.
*
* These functions must be cast to an appropriate function pointer type
* before being called.
*/


這是官方的聲明,從這個函數的註釋能夠看出來了,這是個最基本的用於發送消息的函數。另外,這個函數並不能發送全部類型的消息,只能發送基本的消息。好比,在一些處理器上,咱們必須使用objc_msgSend_stret來發送返回值類型爲結構體的消息,使用objc_msgSend_fpret來發送返回值類型爲浮點類型的消息,而又在一些處理器上,還得使用objc_msgSend_fp2ret來發送返回值類型爲浮點類型的消息。
最關鍵的一點:不管什麼時候,要調用objc_msgSend函數,必需要將函數強制轉換成合適的函數指針類型才能調用。
從objc_msgSend函數的聲明來看,它應該是不帶返回值的,可是咱們在使用中卻能夠強制轉換類型,以便接收返回值。另外,它的參數列表是能夠任意多個的,前提也是要強制函數指針類型。
其實編譯器會根據狀況在objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, 或 objc_msgSendSuper_stret四個方法中選擇一個來調用。若是消息是傳遞給超類,那麼會調用名字帶有」Super」的函數;若是消息返回值是數據結構而不是簡單值時,那麼會調用名字帶有」stret」的函數。

2.SEL
objc_msgSend函數第二個參數類型爲SEL,它是selector在Objc中的表示類型(Swift中是Selector類)。selector是方法選擇器,能夠理解爲區分方法的 ID,而這個 ID 的數據結構是SEL:
typedef struct objc_selector *SEL;
其實它就是個映射到方法的C字符串,你能夠用 Objc 編譯器命令@selector()或者 Runtime 系統的sel_registerName函數來得到一個SEL類型的方法選擇器。
不一樣類中相同名字的方法所對應的方法選擇器是相同的,即便方法名字相同而變量類型不一樣也會致使它們具備相同的方法選擇器,因而 Objc 中方法命名有時會帶上參數類型(NSNumber一堆抽象工廠方法),Cocoa 中有好多長長的方法哦。
3.id
objc_msgSend第一個參數類型爲id,你們對它都不陌生,它是一個指向類實例的指針:
typedef struct objc_object *id;
那objc_object又是啥呢:
struct objc_object { Class isa; };
objc_object結構體包含一個isa指針,根據isa指針就能夠順藤摸瓜找到對象所屬的類。
PS:isa指針不老是指向實例對象所屬的類,不能依靠它來肯定類型,而是應該用class方法來肯定實例對象的類。由於KVO的實現機理就是將被觀察對象的isa指針指向一箇中間類而不是真實的類,這是一種叫作 isa-swizzling 的技術,詳見官方文檔.
4.Class
之因此說isa是指針是由於Class實際上是一個指向objc_class結構體的指針:
typedef struct objc_class *Class;
objc_class裏面的東西多着呢:

[Objective-C]  查看源文件 複製代碼
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
struct objc_class {
     Class isa  OBJC_ISA_AVAILABILITY;
#if  !__OBJC2__
     Class super_class                                        OBJC2_UNAVAILABLE;
     const char *name                                         OBJC2_UNAVAILABLE;
     long version                                             OBJC2_UNAVAILABLE;
     long info                                                OBJC2_UNAVAILABLE;
     long instance_size                                       OBJC2_UNAVAILABLE;
     struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
     struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
     struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
     struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
 
} OBJC2_UNAVAILABLE;

能夠看到運行時一個類還關聯了它的超類指針,類名,成員變量,方法,緩存,還有附屬的協議。在objc_class結構體中:ivars是objc_ivar_list指針;methodLists是指向objc_method_list指針的指針。也就是說能夠動態修改 *methodLists 的值來添加成員方法,這也是Category實現的原理.

相關文章
相關標籤/搜索