iOS Runtime機(jī)制的詳解

iOS Runtime機(jī)制的詳解

前要

將原代碼轉(zhuǎn)換為可執(zhí)行程序需要3步:編譯·鏈接·運(yùn)行。不同的編譯語(yǔ)言在這個(gè)三個(gè)步驟中鎖進(jìn)行的操作有所不同。

1. 什么是runtime

Runtime是用C和匯編編寫(xiě)的用于實(shí)現(xiàn)OC動(dòng)態(tài)語(yǔ)言機(jī)制的開(kāi)源庫(kù)。runtime簡(jiǎn)稱(chēng)運(yùn)行時(shí),就是系統(tǒng)在運(yùn)行的時(shí)候一些機(jī)制。為我們提供了在程序在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建和檢查對(duì)象,修改類(lèi)和對(duì)象的方法。

2. OC與runtime的交互層級(jí)

OC與runtime系統(tǒng)在三個(gè)層級(jí)上進(jìn)行不同的交互。

  • runtime與OC的源代碼交互。
  • runtime與Foundation框架的NSObject類(lèi)定義的方法。
  • runtime的函數(shù)直接被調(diào)用。

大部分時(shí)間開(kāi)發(fā)者只需要專(zhuān)注于OC代碼就可以,runtime系統(tǒng)自動(dòng)在幕后運(yùn)作。

3. 靜態(tài)類(lèi)型語(yǔ)言與動(dòng)態(tài)類(lèi)型語(yǔ)言

  • 靜態(tài)類(lèi)型語(yǔ)言:變量的數(shù)據(jù)類(lèi)型在編譯時(shí)就可以確定的語(yǔ)言,多數(shù)靜態(tài)類(lèi)型的語(yǔ)言要求在使用變量之前必須聲明類(lèi)型。C,C++,Java,C# 都屬于靜態(tài)類(lèi)型語(yǔ)言。

  • 動(dòng)態(tài)類(lèi)型語(yǔ)言:變量的數(shù)據(jù)類(lèi)型在運(yùn)行時(shí)確定的語(yǔ)言,變量在使用之前不需要變量類(lèi)型聲明,通常的變量類(lèi)型是被賦值的那個(gè)變量的類(lèi)型。Python,ruby,OC,js這些都是動(dòng)態(tài)類(lèi)型的語(yǔ)言。

4. C 和OC的函數(shù)調(diào)用對(duì)比

  • C函數(shù)的調(diào)用在編譯的時(shí)候會(huì)決定會(huì)調(diào)用哪個(gè)函數(shù),編譯完之后直接順序執(zhí)行,無(wú)任何二義性。
  • OC函數(shù)的調(diào)用通過(guò)消息發(fā)送,編譯時(shí)并不能決定真正調(diào)用哪個(gè)函數(shù)(在編譯階段OC可以調(diào)用任何函數(shù),即使這個(gè)函數(shù)并未實(shí)現(xiàn),只要聲明過(guò)就不會(huì)報(bào)錯(cuò),而C語(yǔ)言會(huì)報(bào)錯(cuò)),只有在真正運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)的名稱(chēng)找到具體對(duì)應(yīng)函數(shù)來(lái)調(diào)用。

5. runtime的具體實(shí)現(xiàn)

我們寫(xiě)得OC代碼,在運(yùn)行時(shí)候也是轉(zhuǎn)換成了runtime方式運(yùn)行的。更好的去了解runtime能夠幫我們更深入的掌握OC語(yǔ)言。每一個(gè)OC方法,底層必然有一個(gè)與之對(duì)應(yīng)的runtime方法。

// 當(dāng)我們寫(xiě)下這樣的代碼
[tableView cellForRowAtIndexPath:indexPath];
// 在編譯時(shí),runtime會(huì)將上述代碼轉(zhuǎn)換成【發(fā)送消息】
objc_msgSend(tableView, @selector(cellForRowAtIndexPath:),indexPath);

6. 常見(jiàn)runtime方法

獲取屬性列表

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([slef class], &count) ;
for (unsigned int i = 0; i < count; i++ ) {
    Method method = methodList[i];
    NSLog(@"method ----->%@",NSStringFromSelector(method_getName(method)));
}

獲取成員變量列表

Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i = 0; i < count; i++ ) {
    Ivar myIvar = ivarList[i];
    const char *ivarName = ivar_getName(myIvar);
    NSLog(@"Ivar -----> %@",[NSString stringWithUFT8String:ivarName]);
}

獲取協(xié)議列表

__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i; i<count; i++)  {
    Protocol *myProtocol = protocolList[i];
    const char *protocolName = protocol_getName(myProtocol);
    NSLog(@"protocol -----> %@", [NSString stringWithUTF8String:protocolName]);
}

現(xiàn)在有一個(gè)Person類(lèi),和Person類(lèi)創(chuàng)建的xiaoming對(duì)象,和test1 和test2方法。

獲得類(lèi)方法

Class PersonClass = object_getClass([Person class]);

SEL oriSEL = @selectot(test1);
Method oriMethod = class_getInstanceMethod([xiaoming Class], oriSEL);

獲得實(shí)例方法

Class PersonClass = object_getClass([xiaoming class]);
SEL oriSEL = @selectot(test2);
Method cusMethod = class_getInstanceMethod([xiaoming class], oriSEL);

添加方法

BOOL addsucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));

替換原方法實(shí)現(xiàn)

class_replaceMethod(toolClaa, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMetod));

交換方法

method_exchangeImplementations(oriMethod, cusMethod);

7. 常規(guī)作用

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

8.代碼實(shí)現(xiàn)

若要使用runtime,需要先引入頭文件import <objc/runtime.h>

動(dòng)態(tài)變量控制

在程序中xiaoming的age是10,后來(lái)被runtime修改成了20,看下怎么做到的。

  1. 動(dòng)態(tài)獲取xiaoming 類(lèi)中的所有屬性包括私有屬性。

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

  2. 遍歷屬性找到對(duì)應(yīng)的name

    const char *varName = ivar_getName(var);

  3. 修改對(duì)應(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);
        if ([name isEqualToString:@"_age"]) {
            objc_setIvar(self.xiaoMing, var , @"20");
            break;
        }
    }
    NSLog(@"xiao ming's age is %@", self.xiaoMing.age);
}

動(dòng)態(tài)添加方法

在程序中假設(shè)XiaoMing沒(méi)有g(shù)uess方法,后來(lái)被Runtime添加了一個(gè)叫g(shù)uess的方法,最終在調(diào)用guess方法做出響應(yīng)。那么Runtime如何做到的呢?

  • 動(dòng)態(tài)給XiaoMing類(lèi)中添加guess方法:
/*
 * (IMP)guessAnswer 意思是guessAnswer的地址指針
 * "v@:" v:代表返回值void,如果是i代表int,@:代表id sel,“:”代表SEL_cmd
 * “v@:@@” 意思是,兩個(gè)參數(shù)的沒(méi)有返回值。****
 */
class_addMethod([self.xiaoMing class], @selector(guess), (IMP)guessAnswer, "v@:");
  • 調(diào)用guess方法的響應(yīng)時(shí)間:
[self.xiaoMing performSelector:@selector(guess)];
  • 編寫(xiě)guessAnswer的實(shí)現(xiàn):
// void 前面沒(méi)有 + - 號(hào),因?yàn)槭荂代碼
// 必須有兩個(gè)指定參數(shù) id self , SEL_cmd
void guessAnswer(id self, SEL_cmd) {
    NSLog(@"i am from beijing");
}
  • 參考代碼
- (void)answer {
    class_addMethod([self.xiaoMing class], @selector(guess), (IMP)guessAnswer, "v@:");
    if ([self.xiaoMing respondsToSelector:@selector(guess)]) {
        [self.xiaoming performSelector:@selecttor:(guess)];
    } else {
        NSLog(@"there is no guess func");
    }
}

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

動(dòng)態(tài)交換兩個(gè)方法的實(shí)現(xiàn)

在程序中,假設(shè)XiaoMing類(lèi)中有test1 和test2 這兩個(gè)方法,如何使用Runtime對(duì)2個(gè)方法的調(diào)用和實(shí)現(xiàn)相互調(diào)換?

  • 獲取這個(gè)類(lèi)中的兩個(gè)方法并互換
Method m1 = class_getInstanceMethod([self.xiaoMing class], @selector(test1));
Method m2 = class_getInstanceMethod([self.xiaoMing class], @selector(test2));
method_exchangeImplementations(m1, m2);// 交換完成,

攔截并替換方法

在程序中,假設(shè)XiaoMing類(lèi)有test1方法。但是出于某種原因我們改變這個(gè)方法的實(shí)現(xiàn),但又不能去動(dòng)它的源碼,這個(gè)時(shí)候Runtime就出現(xiàn)了。

  • 我們先新增一個(gè)Tool類(lèi),然后自己實(shí)現(xiàn)一個(gè) change方法。通過(guò)Runtime吧tes1 替換成change。
Class PersonClass = object_getClass([Person class]);
Class ToolClass = object_getClass([Tool class]);

// 原方法的SEL和Method
SEL oriSEL = @selector(test1);
Method oriMethod = class_getInstanceMethod(PersonClass, oriSEL);
//交換SEL和Method
SEL cusSEL = @selector(change);
Method cusMethod = class_getInstanceMehtod(ToolClass, cusSEL);
// 先嘗試給原方法添加實(shí)現(xiàn),這里為了避免原方法未實(shí)現(xiàn)的情況
BOOL addSucc = class_addMethod(PersonClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));

if (addSucc) {
    // 添加成功:將原方法的實(shí)現(xiàn)替換到交換方法的實(shí)現(xiàn)
    class_replaceMethod(ToolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else {
// 添加失敗:說(shuō)明原方法已經(jīng)實(shí)現(xiàn),之間替換兩個(gè)方法即可。
method_exchangeImplementations(oriMethod, cusMethod);
}

在現(xiàn)有方法上增加額外功能

有這樣一個(gè)場(chǎng)景,出于某些需求,我們需要跟蹤記錄app中按鈕的點(diǎn)擊次數(shù)和頻率,如何解決?當(dāng)然通過(guò)集成按鈕類(lèi)或者通過(guò)類(lèi)別實(shí)現(xiàn)是一個(gè)方法,但是會(huì)帶來(lái)其他問(wèn)題比如,別人不一定實(shí)例化你的子類(lèi),或者其他類(lèi)別也實(shí)現(xiàn)了點(diǎn)擊方法導(dǎo)致不確定會(huì)調(diào)用哪一個(gè),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:for:Event:);
        Method cusMethod = class_getinstanceMethod(selfClass, cusSEL);
        
        BOOL addSucc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
        if (addSucc) {
            class_replaceMehtod(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方法會(huì)在類(lèi)第一次加載的時(shí)候調(diào)用,調(diào)用的時(shí)間比較靠前,適合在這里做方法交換,在程序中只會(huì)執(zhí)行一次。

實(shí)現(xiàn)NSCoding的自動(dòng)歸檔和解檔

如果你實(shí)現(xiàn)過(guò)自定義模型數(shù)據(jù)持久化過(guò)程,那么你肯定明白,如果一個(gè)數(shù)據(jù)模型有很多屬性,那么我們需要對(duì)每個(gè)屬性實(shí)現(xiàn)一邊encodeObject和decodeObjectForKey方法,如果這樣的模型又多了很多個(gè),這還真是一個(gè)十分麻煩的事情。接下來(lái)看下簡(jiǎn)單的實(shí)現(xiàn)。

// 假設(shè)現(xiàn)在有一個(gè)Movie類(lèi),有3個(gè)屬性
// .h
#import <Foundation/Foundation.h>

// 1. 如果想要當(dāng)前類(lèi)可以實(shí)現(xiàn)歸檔和反歸檔,需要遵守NSCoding協(xié)議。
@interface Movie : NSObject<NSCoding>

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

@end

// 如果是正常寫(xiě)法。.m文件應(yīng)該是這樣的:
//.m 


#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 個(gè)屬性每個(gè)都寫(xiě)一遍豈不是很煩
// 有了runtime 我們可以簡(jiǎn)單實(shí)現(xiàn)
//.m
#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[i];
        // 查看成員變量
        const cahr *name = ivar_getName(ivar);
        // 歸檔
        NSString *key = [NSString stringWithUTF8String:name];
        id value = [self valueForKey:key];
        [encoder encdeObject: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位置對(duì)應(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

這樣的方式實(shí)現(xiàn),不管有多少個(gè)屬性,幾行代碼就搞定了。怎么,還嫌麻煩,下面是更簡(jiǎn)單的方法:

// 我們把encodeWithCoder和initWithCoder 這兩個(gè)方法抽成宏
#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 *iars = class_copyIvarList([A class], &count) ;\
for (int i = 0; i < count; i++ ) {\
Ivar *iavr = 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 {
    encoderRuntime(Movie);
}

- (id)initWithCoder:(NSCoder *)decoder {
    initCoderRuntime(Movie);
}
@end
// 這樣我們吧兩個(gè)單獨(dú)放到文件里面,以后需要持久化數(shù)據(jù)模型就只調(diào)用這兩個(gè)宏

實(shí)現(xiàn)字典和模型的自動(dòng)轉(zhuǎn)換

字典轉(zhuǎn)模型的應(yīng)用可以說(shuō)是每個(gè)APP都需要使用的場(chǎng)景,雖然方式策略各有不同,但是原理都是一致的,遍歷模型中的所有屬性,根據(jù)模型的屬性名去字典中查找key,取出對(duì)應(yīng)的值給模型屬性賦值。例如:JSONModel,MJExtension都是通過(guò)這種方式。

  • 先實(shí)現(xiàn)最外層的屬性轉(zhuǎn)換
// 創(chuàng)建對(duì)應(yīng)模型對(duì)象
id objc = [[self alloc] init];
unsigned int count = 0;
// 1 獲取成員變量屬性組
Ivar *ivarList = class_copyIvarList(self, &count);
// 2 遍歷所有成員屬性名,逐個(gè)去字典中取出相應(yīng)value給模型屬性賦值
for (int i = 0; i < count; i++) {
    Ivar ivar = ivarList[i];
    const char *name = ivar_getName(ivar);
    NSString *ivarName = [NSString stringWithUTF8String:name];
    // _成員屬性名 轉(zhuǎn)換成字典的key
    NSString *key = [ivarName subStringFromIndex:1];
    // 字典取值
    id value = dict[key];
    // 獲取成員屬性類(lèi)型
    NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
}

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

  • 內(nèi)層數(shù)組字典轉(zhuǎn)換
if ([value isKindeClass:[NSDictionary class]] && ![ivarType containsString:@"NS"])  {
    // 是字典對(duì)象,并且屬性名對(duì)應(yīng)的類(lèi)型是自定義類(lèi)型
    // 處理字符串@\"User\" - > User
    invarType = [ivarType stringByReplaceingOccurrencesOfString:@"@" withString:@""];
    ivarType = [ivarType stringByReplaceingOccurrencesOfString:@"\" withString:@""];
    // 自定義對(duì)象并且值是字典
    // value : user字典-> User模型
    // 獲取模型(user)類(lèi)對(duì)象
    Class modelClass = NSClassFromString(ivarType);
    
    // 字典轉(zhuǎn)模型
    if (modelClass) {
        value = [modelClass objectWithDict:value];
    }
}

if ([value isKindOfClass:[NSArray class]]) {
    // 判斷對(duì)應(yīng)類(lèi)有沒(méi)有實(shí)現(xiàn)字典數(shù)組轉(zhuǎn)模型數(shù)組的協(xié)議
    if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
        // 轉(zhuǎn)換成id類(lèi)型,就能調(diào)用任何對(duì)象方法
        id idSelf = self;
        // 獲取數(shù)組中字典對(duì)應(yīng)的模型
        NSString *type = [idSelf arrContainModelClass][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];
        }
        value = arrM;
    }
}

我覺(jué)得系統(tǒng)自帶的KVC模式字典轉(zhuǎn)模型就挺好,假設(shè)Movie就是一個(gè)模型對(duì)象,dict是一個(gè)需要轉(zhuǎn)化的[movie setValuesForKeysWithDictionary:dict]; 這個(gè)是系統(tǒng)自帶的字典轉(zhuǎn)模型的方法。不過(guò)市使用這個(gè)方法的時(shí)候需要在模型里再實(shí)現(xiàn)一個(gè)方法才行:
- (void)setValue:(id)value forUndefinedKey:(NSString *)key,重寫(xiě)這個(gè)方法是為了實(shí)現(xiàn)兩個(gè)目的:

  1. 模型中的屬性和字典中的key不一致的情況,比如字典中的id,我們需要把他賦值給uid屬性。
  2. 字典中屬性比模型中屬性還多的情況。

如果出現(xiàn)上面兩種情況而沒(méi)有實(shí)現(xiàn)下面這個(gè)方法,程序會(huì)崩潰:

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

7.集中參數(shù)概念

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

1.objec_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 fucntion pointer type
 * before being called
 */

這是官方聲明,從這個(gè)函數(shù)可以看出來(lái),這是個(gè)最基本的用于發(fā)消息的函數(shù)。另外,這個(gè)函數(shù)并不能發(fā)送所有的消息類(lèi)型,只能發(fā)送基本的消息。比如,在一些處理器上,我們必須使用objc_msgSend_stret來(lái)發(fā)送返回值類(lèi)型為結(jié)構(gòu)體的消息,使用objec_msgSend_fpret來(lái)發(fā)送返回值類(lèi)型是浮點(diǎn)型的消息,而又在一些處理器上,還得使用objc_msgSend_fp2ret來(lái)發(fā)送返回值類(lèi)型為浮點(diǎn)類(lèi)型的消息。

關(guān)鍵一點(diǎn):無(wú)論何時(shí),要調(diào)用objc_msgSend函數(shù),必須要將函數(shù)強(qiáng)制轉(zhuǎn)換成何時(shí)的函數(shù)指針類(lèi)型才能調(diào)用。

從objc_msgSend函數(shù)的聲明來(lái)看,他應(yīng)該是不帶返回值的,但是我們?cè)谑褂弥锌梢詮?qiáng)制轉(zhuǎn)換類(lèi)型。一邊接收返回值,另外,它的參數(shù)是可以任意多個(gè)的,前提是也要強(qiáng)制函數(shù)指針類(lèi)型。

其實(shí),編譯器會(huì)根據(jù)objc_msgSend, objc_msgSend_stret, objc_msgSendSuper或obc_msgSendSuper_strect 四個(gè)方法中選擇一個(gè)調(diào)用,如果消息是傳遞超類(lèi),那么會(huì)調(diào)“super”函數(shù),如果消息返回值是結(jié)構(gòu)體而不是簡(jiǎn)單值,那么會(huì)調(diào)用名字帶有"stret"的函數(shù)

2. SEL

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

3. id

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

4.class

之所以說(shuō)isa是指針是因?yàn)镃lass其實(shí)是一個(gè)指向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;

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

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

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