runtime詳解

公司項目用到一個三方開源庫,里面有個bug,不能改動源碼,我想來想去,只能通過runtime這個萬能的手段來解決。但是runtime 并不怎么會用,怎么辦,馬上學(xué)習(xí)唄。說到runtime,它是Objective-c里面最核心的技術(shù),被人們傳呼的神乎其神,但是感覺有一層神秘的面紗籠罩其上,畢竟使用場景不多,相信大多數(shù)開發(fā)者都不會熟練的運用。而網(wǎng)絡(luò)上也有無數(shù)的文章來講解runtime,但是真的非常的亂,非常的碎片化,很少有講解的比較全面的。

最初是在onevcat的博客上看到runtime的runtime的博客,說句實話,看完后我還是蒙的,這里面主要講了一下runtime 比較核心的功能-Method Swizzling,不過看完后還是有些不知如何下手的感覺。下面是我自己對runtime的整理,從零開始,由淺入深,并且?guī)Я藥讉€runtime實際的應(yīng)用場景。看完之后,你可以再回過頭來看喵神的這篇文章,應(yīng)該就能看的懂了。

一:基本概念

Runtime基本是用C和匯編寫的,可見蘋果為了動態(tài)系統(tǒng)的高效而作出的努力。你可以在這里下到蘋果維護的開源代碼。蘋果和GNU各自維護一個開源的runtime版本,這兩個版本之間都在努力的保持一致。Objective-C 從三種不同的層級上與 Runtime 系統(tǒng)進行交互,分別是通過 Objective-C 源代碼,通過 Foundation 框架的NSObject類定義的方法,通過對 runtime 函數(shù)的直接調(diào)用。大部分情況下你就只管寫你的Objc代碼就行,runtime 系統(tǒng)自動在幕后辛勤勞作著。

  • RunTime簡稱運行時,就是系統(tǒng)在運行的時候的一些機制,其中最主要的是消息機制。
  • 對于C語言,函數(shù)的調(diào)用在編譯的時候會決定調(diào)用哪個函數(shù),編譯完成之后直接順序執(zhí)行,無任何二義性。
  • OC的函數(shù)調(diào)用成為消息發(fā)送。屬于動態(tài)調(diào)用過程。在編譯的時候并不能決定真正調(diào)用哪個函數(shù)(事實證明,在編 譯階段,OC可以調(diào)用任何函數(shù),即使這個函數(shù)并未實現(xiàn),只要申明過就不會報錯。而C語言在編譯階段就會報錯)。
  • 只有在真正運行的時候才會根據(jù)函數(shù)的名稱找 到對應(yīng)的函數(shù)來調(diào)用。

二:runtime的具體實現(xiàn)

我們寫的oc代碼,它在運行的時候也是轉(zhuǎn)換成了runtime方式運行的,更好的理解runtime,也能幫我們更深的掌握oc語言。
每一個oc的方法,底層必然有一個與之對應(yīng)的runtime方法。

image
image
  • 當我們用OC寫下這樣一段代碼
    [tableView cellForRowAtIndexPath:indexPath];

  • 在編譯時RunTime會將上述代碼轉(zhuǎn)化成[發(fā)送消息]
    objc_msgSend(tableView, @selector(cellForRowAtIndexPath:),indexPath);

三:常見方法

unsigned int count;

  • 獲取屬性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
   for (unsigned int i=0; i<count; i++) {
       const char *propertyName = property_getName(propertyList[i]);
       NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
   }
  • 獲取方法列表
   Method *methodList = class_copyMethodList([self class], &count);
   for (unsigned int i; i<count; i++) {
       Method method = methodList[i];
       NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
   }
  • 獲取成員變量列表

    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]);
      } 
    
  • 獲取協(xié)議列表

__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]);
   }

現(xiàn)在有一個Person類,和person創(chuàng)建的xiaoming對象,有test1和test2兩個方法

  • 獲得類方法
Class PersonClass = object_getClass([Person class]);
SEL oriSEL = @selector(test1);
Method oriMethod = class_getInstanceMethod(xiaomingClass, oriSEL);
  • 獲得實例方法
Class PersonClass = object_getClass([xiaoming class]);
SEL oriSEL = @selector(test2);
Method cusMethod = class_getInstanceMethod(xiaomingClass, oriSEL);
  • 添加方法
BOOL addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
  • 替換原方法實現(xiàn)
class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
  • 交換兩個方法
method_exchangeImplementations(oriMethod, cusMethod);

四:常見作用

  • 動態(tài)的添加對象的成員變量和方法
  • 動態(tài)交換兩個方法的實現(xiàn)
  • 攔截并替換方法
  • 在方法上增加額外功能
  • 實現(xiàn)NSCoding的自動歸檔和解檔
  • 實現(xiàn)字典轉(zhuǎn)模型的自動轉(zhuǎn)換

五:代碼實現(xiàn)

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

1. 動態(tài)變量控制

在程序中,xiaoming的age是10,后來被runtime變成了20,來看看runtime是怎么做到的。

1.動態(tài)獲取XiaoMing類中的所有屬性[當然包括私有]

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

2.遍歷屬性找到對應(yīng)name字段

const char *varName = ivar_getName(var);

3.修改對應(yīng)的字段值成20

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

4.代碼參考

-(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.動態(tài)添加方法

在程序當中,假設(shè)XiaoMing的中沒有guess這個方法,后來被Runtime添加一個名字叫g(shù)uess的方法,最終再調(diào)用guess方法做出相應(yīng)。那么,Runtime是如何做到的呢?

1.動態(tài)給XiaoMing類中添加guess方法:

 class_addMethod([self.xiaoMing class], @selector(guess), (IMP)guessAnswer, "v@:");

這里參數(shù)地方說明一下:

(IMP)guessAnswer 意思是guessAnswer的地址指針;
"v@:" 意思是,v代表無返回值void,如果是i則代表int;@代表 id sel; : 代表 SEL _cmd;
“v@:@@” 意思是,兩個參數(shù)的沒有返回值。

2.調(diào)用guess方法響應(yīng)事件:

[self.xiaoMing performSelector:@selector(guess)];

3.編寫guessAnswer的實現(xiàn):

void guessAnswer(id self,SEL _cmd){
NSLog(@"i am from beijing");
}

這個有兩個地方留意一下:

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

4.代碼參考

 -(void)answer{
     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:動態(tài)交換兩個方法的實現(xiàn)

在程序當中,假設(shè)XiaoMing的中有test1test2這兩個方法,后來被Runtime交換方法后,每次調(diào)動test1 的時候就會去執(zhí)行test2,調(diào)動test2 的時候就會去執(zhí)行test1, 。那么,Runtime是如何做到的呢?

  1. 獲取這個類中的兩個方法并交換
Method m1 = class_getInstanceMethod([self.xiaoMing class], @selector(test1));
    Method m2 = class_getInstanceMethod([self.xiaoMing class], @selector(test2));
    method_exchangeImplementations(m1, m2);

交換方法之后,以后每次調(diào)用這兩個方法都會交換方法的實現(xiàn)

4:攔截并替換方法

在程序當中,假設(shè)XiaoMing的中有test1這個方法,但是由于某種原因,我們要改變這個方法的實現(xiàn),但是又不能去動它的源代碼(正如一些開源庫出現(xiàn)問題的時候),這個時候runtime就派上用場了。

我們先增加一個tool類,然后寫一個我們自己實現(xiàn)的方法-change,
通過runtime把test1替換成change。

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);
    
    ////先嘗試給源方法添加實現(xiàn),這里是為了避免源方法沒有實現(xiàn)的情況
    
    BOOL addSucc = class_addMethod(PersionClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
    if (addSucc) {
          // 添加成功:將源方法的實現(xiàn)替換到交換方法的實現(xiàn)     
        class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
               
    }else {
    //添加失?。赫f明源方法已經(jīng)有實現(xiàn),直接將兩個方法的實現(xiàn)交換即
method_exchangeImplementations(oriMethod, cusMethod);  
  }
 

5:在方法上增加額外功能

有這樣一個場景,出于某些需求,我們需要跟蹤記錄APP中按鈕的點擊次數(shù)和頻率等數(shù)據(jù),怎么解決?當然通過繼承按鈕類或者通過類別實現(xiàn)是一個辦法,但是帶來其他問題比如別人不一定會去實例化你寫的子類,或者其他類別也實現(xiàn)了點擊方法導(dǎo)致不確定會調(diào)用哪一個,runtime可以這樣解決:

@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方法會在類第一次加載的時候被調(diào)用,調(diào)用的時間比較靠前,適合在這個方法里做方法交換,方法交換應(yīng)該被保證,在程序中只會執(zhí)行一次。

6.實現(xiàn)NSCoding的自動歸檔和解檔

如果你實現(xiàn)過自定義模型數(shù)據(jù)持久化的過程,那么你也肯定明白,如果一個模型有許多個屬性,那么我們需要對每個屬性都實現(xiàn)一遍encodeObjectdecodeObjectForKey方法,如果這樣的模型又有很多個,這還真的是一個十分麻煩的事情。下面來看看簡單的實現(xiàn)方式。
假設(shè)現(xiàn)在有一個Movie類,有3個屬性,它的h文件這這樣的

#import <Foundation/Foundation.h>

//1. 如果想要當前類可以實現(xiàn)歸檔與反歸檔,需要遵守一個協(xié)議NSCoding
@interface Movie : NSObject<NSCoding>

@property (nonatomic, copy) NSString *movieId;
@property (nonatomic, copy) NSString *movieName;
@property (nonatomic, copy) NSString *pic_url;

@end

如果是正常寫法, m文件應(yīng)該是這樣的:

#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的實現(xiàn)方式:

#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位置對應(yīng)的成員變量
        Ivar ivar = ivars[i];
        // 查看成員變量
        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位置對應(yīng)的成員變量
        Ivar ivar = ivars[i];
        // 查看成員變量
        const char *name = ivar_getName(ivar);
       // 歸檔
       NSString *key = [NSString stringWithUTF8String:name];
      id value = [decoder decodeObjectForKey:key];
       // 設(shè)置到成員變量身上
        [self setValue:value forKey:key];
            
        }
        free(ivars);
    } 
    return self;
}
@end

這樣的方式實現(xiàn),不管有多少個屬性,寫這幾行代碼就搞定了。怎么,還嫌麻煩,下面看看更加簡便的方法:兩句代碼搞定。
我們把encodeWithCoderinitWithCoder這兩個方法抽成宏

#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[i];\
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[i];\
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

我們可以把這兩個宏單獨放到一個文件里面,這里以后需要進行數(shù)據(jù)持久化的模型都可以直接使用這兩個宏。

7.實現(xiàn)字典轉(zhuǎn)模型的自動轉(zhuǎn)換

字典轉(zhuǎn)模型的應(yīng)用可以說是每個app必然會使用的場景,雖然實現(xiàn)的方式略有不同,但是原理都是一致的:遍歷模型中所有屬性,根據(jù)模型的屬性名,去字典中查找key,取出對應(yīng)的值,給模型的屬性賦值。
像幾個出名的開源庫:JSONModel,MJExtension等都是通過這種方式實現(xiàn)的。

  • 先實現(xiàn)最外層的屬性轉(zhuǎn)換
   // 創(chuàng)建對應(yīng)模型對象
    id objc = [[self alloc] init];

    unsigned int count = 0;

    // 1.獲取成員屬性數(shù)組
    Ivar *ivarList = class_copyIvarList(self, &count);

    // 2.遍歷所有的成員屬性名,一個一個去字典中取出對應(yīng)的value給模型屬性賦值
    for (int i = 0; i < count; i++) {

        // 2.1 獲取成員屬性
        Ivar ivar = ivarList[i];

        // 2.2 獲取成員屬性名 C -> OC 字符串
       NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];

        // 2.3 _成員屬性名 => 字典key
        NSString *key = [ivarName substringFromIndex:1];

        // 2.4 去字典中取出對應(yīng)value給模型屬性賦值
        id value = dict[key];

        // 獲取成員屬性類型
        NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        }

如果模型比較簡單,只有NSString,NSNumber等,這樣就可以搞定了。但是如果模型含有NSArray,或者NSDictionary等,那么我們還需要進行第二步轉(zhuǎn)換。

  • 內(nèi)層數(shù)組,字典的轉(zhuǎn)換
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType containsString:@"NS"]) { 

             //  是字典對象,并且屬性名對應(yīng)類型是自定義類型
            // 處理類型字符串 @\"User\" -> User
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
            // 自定義對象,并且值是字典
            // value:user字典 -> User模型
            // 獲取模型(user)類對象
            Class modalClass = NSClassFromString(ivarType);

            // 字典轉(zhuǎn)模型
            if (modalClass) {
                // 字典轉(zhuǎn)模型 user
                value = [modalClass objectWithDict:value];
            }

        }
        
        if ([value isKindOfClass:[NSArray class]]) {
            // 判斷對應(yīng)類有沒有實現(xiàn)字典數(shù)組轉(zhuǎn)模型數(shù)組的協(xié)議
            if ([self respondsToSelector:@selector(arrayContainModelClass)]) {

                // 轉(zhuǎn)換成id類型,就能調(diào)用任何對象的方法
                id idSelf = self;

                // 獲取數(shù)組中字典對應(yīng)的模型
                NSString *type =  [idSelf arrayContainModelClass][key];

                // 生成模型
                Class classModel = NSClassFromString(type);
                NSMutableArray *arrM = [NSMutableArray array];
                // 遍歷字典數(shù)組,生成模型數(shù)組
                for (NSDictionary *dict in value) {
                    // 字典轉(zhuǎn)模型
                    id model =  [classModel objectWithDict:dict];
                    [arrM addObject:model];
                }

                // 把模型數(shù)組賦值給value
                value = arrM;

            }
        }

我自己覺得系統(tǒng)自帶的KVC模式字典轉(zhuǎn)模型就挺好的,假設(shè)movie是一個模型對象,dict 是一個需要轉(zhuǎn)化的 [movie setValuesForKeysWithDictionary:dict]; 這個是系統(tǒng)自帶的字典轉(zhuǎn)模型方法,個人感覺也還是挺好用的,不過使用這個方法的時候需要在模型里面再實現(xiàn)一個方法才行,
- (void)setValue:(id)value forUndefinedKey:(NSString *)key 重寫這個方法為了實現(xiàn)兩個目的:1. 模型中的屬性和字典中的key不一致的情況,比如字典中有個id,我們需要把它賦值給uid屬性;2. 字典中屬性比模型的屬性還多的情況。
如果出現(xiàn)以上兩種情況而沒有實現(xiàn)這個方法的話,程序就會崩潰。
這個方法的實現(xiàn):

- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
    if ([key isEqualToString:@"id"]) {
        self.uid = value;
    }
}

六.幾個參數(shù)概念

以上的幾種方法應(yīng)該算是runtime在實際場景中所應(yīng)用的大部分的情況了,平常的編碼中差不多足夠用了。
如果從頭仔細看到尾,相信你基本的用法應(yīng)該會了,雖然會用是主要的目的,有幾個基本的參數(shù)概念還是要了解一下的。

1.objc_msgSend

/* 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. 
*/

這是官方的聲明,從這個函數(shù)的注釋可以看出來了,這是個最基本的用于發(fā)送消息的函數(shù)。另外,這個函數(shù)并不能發(fā)送所有類型的消息,只能發(fā)送基本的消息。比如,在一些處理器上,我們必須使用objc_msgSend_stret來發(fā)送返回值類型為結(jié)構(gòu)體的消息,使用objc_msgSend_fpret來發(fā)送返回值類型為浮點類型的消息,而又在一些處理器上,還得使用objc_msgSend_fp2ret來發(fā)送返回值類型為浮點類型的消息。
最關(guān)鍵的一點:無論何時,要調(diào)用objc_msgSend函數(shù),必須要將函數(shù)強制轉(zhuǎn)換成合適的函數(shù)指針類型才能調(diào)用。
objc_msgSend函數(shù)的聲明來看,它應(yīng)該是不帶返回值的,但是我們在使用中卻可以強制轉(zhuǎn)換類型,以便接收返回值。另外,它的參數(shù)列表是可以任意多個的,前提也是要強制函數(shù)指針類型。
其實編譯器會根據(jù)情況在objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, 或 objc_msgSendSuper_stret四個方法中選擇一個來調(diào)用。如果消息是傳遞給超類,那么會調(diào)用名字帶有”Super”的函數(shù);如果消息返回值是數(shù)據(jù)結(jié)構(gòu)而不是簡單值時,那么會調(diào)用名字帶有”stret”的函數(shù)。

2.SEL

objc_msgSend函數(shù)第二個參數(shù)類型為SEL,它是selector在Objc中的表示類型(Swift中是Selector類)。selector是方法選擇器,可以理解為區(qū)分方法的 ID,而這個 ID 的數(shù)據(jù)結(jié)構(gòu)是SEL:
typedef struct objc_selector *SEL;
其實它就是個映射到方法的C字符串,你可以用 Objc 編譯器命令@selector()或者 Runtime 系統(tǒng)的sel_registerName函數(shù)來獲得一個SEL類型的方法選擇器。
不同類中相同名字的方法所對應(yīng)的方法選擇器是相同的,即使方法名字相同而變量類型不同也會導(dǎo)致它們具有相同的方法選擇器,于是 Objc 中方法命名有時會帶上參數(shù)類型(NSNumber一堆抽象工廠方法),Cocoa 中有好多長長的方法哦。

3.id

objc_msgSend第一個參數(shù)類型為id,大家對它都不陌生,它是一個指向類實例的指針:
typedef struct objc_object *id;
那objc_object又是啥呢:
struct objc_object { Class isa; };
objc_object結(jié)構(gòu)體包含一個isa指針,根據(jù)isa指針就可以順藤摸瓜找到對象所屬的類。
PS:isa指針不總是指向?qū)嵗龑ο笏鶎俚念悾荒芤揽克鼇泶_定類型,而是應(yīng)該用class方法來確定實例對象的類。因為KVO的實現(xiàn)機理就是將被觀察對象的isa指針指向一個中間類而不是真實的類,這是一種叫做 isa-swizzling 的技術(shù),詳見官方文檔.

4.Class

之所以說isa是指針是因為Class其實是一個指向objc_class結(jié)構(gòu)體的指針:
typedef struct objc_class *Class;
objc_class里面的東西多著呢:

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;

可以看到運行時一個類還關(guān)聯(lián)了它的超類指針,類名,成員變量,方法,緩存,還有附屬的協(xié)議。
objc_class結(jié)構(gòu)體中:ivars是objc_ivar_list指針;methodLists是指向objc_method_list指針的指針。也就是說可以動態(tài)修改 *methodLists 的值來添加成員方法,這也是Category實現(xiàn)的原理.

上面講到的所有東西都在Demo里,如果你覺得不錯,還請為我的Demo star一個。

demo下載

image
image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容