Runtime全方位裝逼指南

?楔子
Runtime是什么?見(jiàn)名知意署驻,其概念無(wú)非就是“因?yàn)?Objective-C 是一門(mén)動(dòng)態(tài)語(yǔ)言丹拯,所以它需要一個(gè)運(yùn)行時(shí)系統(tǒng)……這就是 Runtime 系統(tǒng)”云云赶盔。對(duì)博主這種菜鳥(niǎo)而言历恐,Runtime 在實(shí)際開(kāi)發(fā)中寸癌,其實(shí)就是一組C語(yǔ)言的函數(shù)。胡適說(shuō):“多研究些問(wèn)題弱贼,少談些主義”蒸苇,云山霧罩的概念聽(tīng)多了總是容易頭暈,接下來(lái)我們直接從代碼入手學(xué)習(xí) Runtime吮旅。
?
1溪烤、由objc_msgSend說(shuō)開(kāi)去:
Objective-C 中的方法調(diào)用,不是簡(jiǎn)單的方法調(diào)用庇勃,而是發(fā)送消息檬嘀,也就是說(shuō),其實(shí) [receiver message] 會(huì)被編譯器轉(zhuǎn)化為: objc_msgSend(receiver, selector)责嚷,何以證明鸳兽?新建一個(gè)類 MyClass,其.m文件如下:

#import "MyClass.h"
@implementation MyClass

-(instancetype)init{
    if (self = [super init]) {
        [self showUserName];
    }
    return self;
}

-(void)showUserName{
    NSLog(@"Dave Ping");
}

使用 clang 重寫(xiě)命令:

$ clang -rewrite-objc MyClass.m

然后在同一目錄下會(huì)多出一個(gè) MyClass.cpp 文件罕拂,雙擊打開(kāi)揍异,可以看到 init 方法已經(jīng)被編譯器轉(zhuǎn)化為下面這樣:

static instancetype _I_MyClass_init(MyClass * self, SEL _cmd) {
    if (self = ((MyClass *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("MyClass"))}, sel_registerName("init"))) {
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("showUserName"));
    }
    return self;
}

我們要找的就是它:

((void (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("showUserName"))

objc_msgSend 函數(shù)被定義在 objc/message.h 目錄下,其函數(shù)原型是醬紫滴:

OBJC_EXPORT void objc_msgSend(void /* id self, SEL op, ... */ )

?該函數(shù)有兩個(gè)參數(shù)爆班,一個(gè) id 類型衷掷,一個(gè) SEL 類型。

2柿菩、SEL
SEL 被定義在 ?objc/objc.h 目錄下:

typedef struct objc_selector *SEL;

其實(shí)它就是個(gè)映射到方法的C字符串戚嗅,你可以用 Objective-C 編譯器命令 @selector() 或者 Runtime 系統(tǒng)的 sel_registerName 函數(shù)來(lái)獲得一個(gè) SEL 類型的方法選擇器。

3枢舶、id
與 SEL 一樣懦胞,id 也被定義在 ?objc/objc.h 目錄下:

typedef struct objc_object *id;

id 是一個(gè)結(jié)構(gòu)體指針類型,它可以指向 Objective-C 中的任何對(duì)象祟辟。objc_object 結(jié)構(gòu)體定義如下:

struct objc_object { Class isa OBJC_ISA_AVAILABILITY;};

我們通常所說(shuō)的對(duì)象医瘫,就長(zhǎng)這個(gè)樣子,這個(gè)結(jié)構(gòu)體只有一個(gè)成員變量? isa旧困,?對(duì)象可以通過(guò) ?isa 指針找到其所屬的類醇份。isa 是一個(gè) ?Class 類型的成員變量,那么 Class 又是什么呢吼具?

4僚纷、Class
Class 也是一個(gè)結(jié)構(gòu)體指針類型:

typedef struct objc_class *Class;

objc_class 結(jié)構(gòu)體是醬紫滴:

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;

我們通常說(shuō)的?類就長(zhǎng)這樣子:
·Class 也有一個(gè) isa 指針,指向其所屬的元類(meta).
·super_class:指向其超類.
·name:是類名.
·version:是類的版本信息.
·info:是類的詳情.
·instance_size:是該類的實(shí)例對(duì)象的大小.
·ivars:指向該類的成員變量列表.
·methodLists:指向該類的實(shí)例方法列表拗盒,它將方法選擇器和方法實(shí)現(xiàn)地址聯(lián)系起來(lái)怖竭。methodLists 是指向 ·objc_method_list 指針的指針,也就是說(shuō)可以動(dòng)態(tài)修改 *methodLists 的值來(lái)添加成員方法陡蝇,這也是 Category 實(shí)現(xiàn)的原理痊臭,同樣解釋了 Category 不能添加屬性的原因.
·cache:Runtime 系統(tǒng)會(huì)把被調(diào)用的方法存到 ?cache 中(理論上講一個(gè)方法如果被調(diào)用?哮肚,那么它有可能今后還會(huì)被調(diào)用),下次查找的時(shí)候效率更高.
·protocols:指向該類的協(xié)議列表.


說(shuō)到這里有點(diǎn)亂了广匙,我們來(lái)捋一下允趟,當(dāng)我們調(diào)用一個(gè)方法時(shí),其運(yùn)行過(guò)程大致如下:

首先鸦致,Runtime 系統(tǒng)會(huì)把方法調(diào)用轉(zhuǎn)化為消息發(fā)送潮剪,即 objc_msgSend,并且把方法的調(diào)用者分唾,和方法選擇器抗碰,當(dāng)做參數(shù)傳遞過(guò)去.

此時(shí),方法的調(diào)用者會(huì)通過(guò) isa 指針來(lái)找到其所屬的類绽乔,然后在 cache 或者 methodLists 中查找該方法弧蝇,找得到就跳到對(duì)應(yīng)的方法去執(zhí)行.

如果在中沒(méi)有找到該方法,則通過(guò) super_class 往上一級(jí)超類查找(如果一直找到 NSObject 都沒(méi)有找到該方法的話迄汛,這種情況捍壤,我們放到后面消息轉(zhuǎn)發(fā)的時(shí)候再說(shuō)).

前面我們說(shuō) methodLists 指向該類的實(shí)例方法列表實(shí)例方法-方法鞍爱,那么類方法(+方法)存儲(chǔ)在哪兒呢鹃觉?類方法被存儲(chǔ)在元類中,Class 通過(guò) isa 指針即可找到其所屬的元類.


上圖實(shí)線是 super_class 指針睹逃,虛線是 isa 指針盗扇。根元類的超類是NSObject,而 isa 指向了自己沉填。NSObject 的超類為 nil疗隶,也就是它沒(méi)有超類。

5翼闹、使用objc_msgSend
前面我們使用 clang 重寫(xiě)命令斑鼻,看到 Runtime 是如何將方法調(diào)用轉(zhuǎn)化為消息發(fā)送的。我們也可以依樣畫(huà)葫蘆猎荠,來(lái)學(xué)習(xí)使用一下 objc_msgSend坚弱。新建一個(gè)類 TestClass,添加如下方法:

-(void)showAge{
    NSLog(@"24");
}

-(void)showName:(NSString *)aName{
    NSLog(@"name is %@",aName);
}

-(void)showSizeWithWidth:(float)aWidth andHeight:(float)aHeight{
    NSLog(@"size is %.2f * %.2f",aWidth, aHeight);
}

-(float)getHeight{
    return 187.5f;
}

-(NSString *)getInfo{
    return @"Hi, my name is Dave Ping, I'm twenty-four years old in the year, I like apple, nice to meet you.";
}

我們可以像下面這樣关摇,使用 objc_msgSend 依次調(diào)用這些方法:

    TestClass *objct = [[TestClass alloc] init];
    
    ((void (*) (id, SEL)) objc_msgSend) (objct, sel_registerName("showAge"));
    
    ((void (*) (id, SEL, NSString *)) objc_msgSend) (objct, sel_registerName("showName:"), @"Dave Ping");
    
    ((void (*) (id, SEL, float, float)) objc_msgSend) (objct, sel_registerName("showSizeWithWidth:andHeight:"), 110.5f, 200.0f);
    
    float f = ((float (*) (id, SEL)) objc_msgSend_fpret) (objct, sel_registerName("getHeight"));
    NSLog(@"height is %.2f",f);
    
    NSString *info = ((NSString* (*) (id, SEL)) objc_msgSend) (objct, sel_registerName("getInfo"));
    NSLog(@"%@",info);

也許你已經(jīng)注意到荒叶,objc_msgSend 在使用時(shí)都被強(qiáng)制轉(zhuǎn)換了一下,這是因?yàn)?objc_msgSend 這個(gè)函數(shù)至少要有兩個(gè)參數(shù)输虱,一個(gè)id消息接受者些楣,一個(gè)SEL消息名稱。后面三個(gè)點(diǎn)代表參數(shù),是變參愁茁。也就是說(shuō)方法攜帶的參數(shù)蚕钦,可以沒(méi)有,可以有多個(gè)埋市。如果我們把調(diào)用 showAge 方法改成這樣:

objc_msgSend(objct, sel_registerName("showAge"));

Xcode 就會(huì)報(bào)錯(cuò):

Too many arguments to function call, expected 0, have 2.

完整的 objc_msgSend 使用代碼在?這里冠桃。

6、objc_msgSendSuper
編譯器會(huì)根據(jù)情況在 objc_msgSend道宅,objc_msgSend_stret,objc_msgSendSuper胸蛛,objc_msgSendSuper_stret 或 objc_msgSend_fpret 五個(gè)方法中選擇一個(gè)來(lái)調(diào)用污茵。如果消息是傳遞給超類,那么會(huì)調(diào)用 objc_msgSendSuper 方法葬项,如果消息返回值是數(shù)據(jù)結(jié)構(gòu)泞当,就會(huì)調(diào)用 objc_msgSendSuper_stret 方法,如果返回值是浮點(diǎn)數(shù)民珍,則調(diào)用 objc_msgSend_fpret 方法襟士。

這里我們重點(diǎn)說(shuō)一下 objc_msgSendSuper,objc_msgSendSuper 函數(shù)原型如下:

OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )

?當(dāng)我們調(diào)用 [super selector] 時(shí)嚷量,Runtime 會(huì)調(diào)用 objc_msgSendSuper 方法陋桂,objc_msgSendSuper 方法有兩個(gè)參數(shù),super 和 op蝶溶,Runtime 會(huì)把 selector 方法選擇器賦值給 op嗜历。而 super 是一個(gè) objc_super 結(jié)構(gòu)體指針,objc_super 結(jié)構(gòu)體定義如下:

struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained Class class;
#else
    __unsafe_unretained Class super_class;
#endif
    /* super_class is the first class to search */
};

Runtime 會(huì)創(chuàng)建一個(gè) objc_spuer 結(jié)構(gòu)體變量抖所,將其地址作為參數(shù)(super)傳遞給 objc_msgSendSuper梨州,并且將 self 賦值給 receiver:super—>receiver=self.
舉個(gè)栗子,問(wèn)下面的代碼輸出什么:

@implementation Son : Father
- (id)init
{
    self = [super init];
    if (self)
    {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}
@end

答案是全部輸出 Son.
使用 clang 重寫(xiě)命令田轧,發(fā)現(xiàn)上述代碼被轉(zhuǎn)化為:

NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_1, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){ (id)self, (id)class_getSuperclass(objc_getClass("Son")) }, sel_registerName("class"))));

當(dāng)調(diào)用 [super class] 時(shí)暴匠,會(huì)轉(zhuǎn)換成 objc_msgSendSuper 函數(shù):

第一步先構(gòu)造 objc_super 結(jié)構(gòu)體,結(jié)構(gòu)體第一個(gè)成員就是 self傻粘。第二個(gè)成員是 (id)class_getSuperclass(objc_getClass(“Son”)).

第二步是去 Father 這個(gè)類里去找 - (Class)class每窖,沒(méi)有,然后去 NSObject 類去找抹腿,找到了岛请。最后內(nèi)部是使用 objc_msgSend(objc_super->receiver, @selector(class)) 去調(diào)用,此時(shí)已經(jīng)和 [self class] 調(diào)用相同了警绩,所以兩個(gè)輸出結(jié)果都是 Son崇败。

7、?對(duì)象關(guān)聯(lián)
對(duì)象關(guān)聯(lián)允許開(kāi)發(fā)者對(duì)已經(jīng)存在的類在 Category 中添加自定義的屬性

OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);

·object 是源對(duì)象.
·value 是被關(guān)聯(lián)的對(duì)象.
·key 是關(guān)聯(lián)的鍵,objc_getAssociatedObject 方法通過(guò)不同的 key 即可取出對(duì)應(yīng)的被關(guān)聯(lián)對(duì)象.
·policy 是一個(gè)枚舉值后室,表示關(guān)聯(lián)對(duì)象的行為缩膝,從命名就能看出各個(gè)枚舉值的含義:

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

要取出被關(guān)聯(lián)的對(duì)象使用 objc_getAssociatedObject 方法即可,要?jiǎng)h除一個(gè)被關(guān)聯(lián)的對(duì)象岸霹,使用 objc_setAssociatedObject 方法將對(duì)應(yīng)的 key 設(shè)置成 nil 即可:

objc_setAssociatedObject(self, associatedKey, nil, OBJC_ASSOCIATION_COPY_NONATOMIC);

objc_removeAssociatedObjects 方法將會(huì)移除源對(duì)象中所有的關(guān)聯(lián)對(duì)象.
舉個(gè)栗子疾层,假如我們要給 UIButton 添加一個(gè)監(jiān)聽(tīng)單擊事件的 block 屬性,新建 UIButton 的 Category贡避,其.m文件如下:

#import "UIButton+ClickBlock.h"
#import <objc/runtime.h>

static const void *associatedKey = "associatedKey";

@implementation UIButton (ClickBlock)

//Category中的屬性痛黎,只會(huì)生成setter和getter方法,不會(huì)生成成員變量

-(void)setClick:(clickBlock)click{
    objc_setAssociatedObject(self, associatedKey, click, OBJC_ASSOCIATION_COPY_NONATOMIC);
    [self removeTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
    if (click) {
        [self addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
    }
}

-(clickBlock)click{
    return objc_getAssociatedObject(self, associatedKey);
}

-(void)buttonClick{
    if (self.click) {
        self.click();
    }
}

@end

然后在代碼中刮吧,就可以使用 UIButton 的屬性來(lái)監(jiān)聽(tīng)單擊事件了:

    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.frame = self.view.bounds;
    [self.view addSubview:button];
    button.click = ^{
        NSLog(@"buttonClicked");
    };

完整的對(duì)象關(guān)聯(lián)代碼點(diǎn)這里

8湖饱、?自動(dòng)歸檔
博主在學(xué)習(xí) Runtime 之前,歸檔的時(shí)候是醬紫寫(xiě)的:

- (void)encodeWithCoder:(NSCoder *)aCoder{
    [aCoder encodeObject:self.name forKey:@"name"];
    [aCoder encodeObject:self.ID forKey:@"ID"];
}

- (id)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super init]) {
        self.ID = [aDecoder decodeObjectForKey:@"ID"];
        self.name = [aDecoder decodeObjectForKey:@"name"];
    }
    return self;
}

那么問(wèn)題來(lái)了杀捻,如果當(dāng)前 Model 有100個(gè)屬性的話井厌,就需要寫(xiě)100行這種代碼:

[aCoder encodeObject:self.name forKey:@"name"];

想想都頭疼,通過(guò) Runtime 我們就可以輕松解決這個(gè)問(wèn)題:
?1.使用 class_copyIvarList 方法獲取當(dāng)前 Model 的所有成員變量.
2.使用 ivar_getName 方法獲取成員變量的名稱.
3.通過(guò) KVC 來(lái)讀取 Model 的屬性值(encodeWithCoder:)致讥,以及給 Model 的屬性賦值(initWithCoder:).

舉個(gè)栗子仅仆,新建一個(gè) Model 類,其.m文件如下:

#import "TestModel.h"
#import <objc/runtime.h>
#import <objc/message.h>

@implementation TestModel
- (void)encodeWithCoder:(NSCoder *)aCoder{
    unsigned int outCount = 0;
    Ivar *vars = class_copyIvarList([self class], &outCount);
    for (int i = 0; i < outCount; i ++) {
        Ivar var = vars[i];
        const char *name = ivar_getName(var);
        NSString *key = [NSString stringWithUTF8String:name];
        
        // 注意kvc的特性是垢袱,如果能找到key這個(gè)屬性的setter方法墓拜,則調(diào)用setter方法
        // 如果找不到setter方法,則查找成員變量key或者成員變量_key惶桐,并且為其賦值
        // 所以這里不需要再另外處理成員變量名稱的“_”前綴
        id value = [self valueForKey:key];
        [aCoder encodeObject:value forKey:key];
    }
}

- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super init]) {
        unsigned int outCount = 0;
        Ivar *vars = class_copyIvarList([self class], &outCount);
        for (int i = 0; i < outCount; i ++) {
            Ivar var = vars[i];
            const char *name = ivar_getName(var);
            NSString *key = [NSString stringWithUTF8String:name];
            id value = [aDecoder decodeObjectForKey:key];
            [self setValue:value forKey:key];
        }
    }
    return self;
}
@end

完整的自動(dòng)歸檔代碼在這里

9撮弧、字典與模型互轉(zhuǎn)
最開(kāi)始博主是這樣用字典給 Model 賦值的:

-(instancetype)initWithDictionary:(NSDictionary *)dict{
    if (self = [super init]) {
        self.age = dict[@"age"];
        self.name = dict[@"name"];
    }
    return self;
}

可想而知,遇到的問(wèn)題跟歸檔時(shí)候一樣(后來(lái)使用MJExtension)姚糊,這里我們稍微來(lái)學(xué)習(xí)一下其中原理贿衍,字典轉(zhuǎn)模型的時(shí)候:
1.?根據(jù)字典的 key 生成 setter 方法.
2.使用 objc_msgSend 調(diào)用 setter 方法為 Model 的屬性賦值(或者 KVC).

模型轉(zhuǎn)字典的時(shí)候:
?1.調(diào)用 class_copyPropertyList 方法獲取當(dāng)前 Model 的所有屬性.
2.調(diào)用 property_getName 獲取屬性名稱.
3.根據(jù)屬性名稱生成 getter 方法.
4.使用 objc_msgSend 調(diào)用 getter 方法獲取屬性值(或者 KVC).

代碼如下:

#import "NSObject+KeyValues.h"
#import <objc/runtime.h>
#import <objc/message.h>

@implementation NSObject (KeyValues)

//字典轉(zhuǎn)模型
+(id)objectWithKeyValues:(NSDictionary *)aDictionary{
    id objc = [[self alloc] init];
    for (NSString *key in aDictionary.allKeys) {
        id value = aDictionary[key];
        
        /*判斷當(dāng)前屬性是不是Model*/
        objc_property_t property = class_getProperty(self, key.UTF8String);
        unsigned int outCount = 0;
        objc_property_attribute_t *attributeList = property_copyAttributeList(property, &outCount);
        objc_property_attribute_t attribute = attributeList[0];
        NSString *typeString = [NSString stringWithUTF8String:attribute.value];
        if ([typeString isEqualToString:@"@\"TestModel\""]) {
            value = [self objectWithKeyValues:value];
        }
        /**********************/
        
        //生成setter方法,并用objc_msgSend調(diào)用
        NSString *methodName = [NSString stringWithFormat:@"set%@%@:",[key substringToIndex:1].uppercaseString,[key substringFromIndex:1]];
        SEL setter = sel_registerName(methodName.UTF8String);
        if ([objc respondsToSelector:setter]) {
            ((void (*) (id,SEL,id)) objc_msgSend) (objc,setter,value);
        }
    }
    return objc;
}

//模型轉(zhuǎn)字典
-(NSDictionary *)keyValuesWithObject{
    unsigned int outCount = 0;
    objc_property_t *propertyList = class_copyPropertyList([self class], &outCount);
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    for (int i = 0; i < outCount; i ++) {
        objc_property_t property = propertyList[i];
        
        //生成getter方法救恨,并用objc_msgSend調(diào)用
        const char *propertyName = property_getName(property);
        SEL getter = sel_registerName(propertyName);
        if ([self respondsToSelector:getter]) {
            id value = ((id (*) (id,SEL)) objc_msgSend) (self,getter);
            
            /*判斷當(dāng)前屬性是不是Model*/
            if ([value isKindOfClass:[self class]] && value) {
                value = [value keyValuesWithObject];
            }
            /**********************/
            
            if (value) {
                NSString *key = [NSString stringWithUTF8String:propertyName];
                [dict setObject:value forKey:key];
            }
        }
        
    }
    return dict;
}
@end

完整代碼在這里

10贸辈、?動(dòng)態(tài)方法解析
前面我們留下了一點(diǎn)東西沒(méi)說(shuō),那就是如果某個(gè)對(duì)象調(diào)用了不存在的方法時(shí)會(huì)怎么樣肠槽,一般情況下程序會(huì)crash擎淤,錯(cuò)誤信息類似下面這樣:

unrecognized selector sent to instance 0x7fd0a141afd0

但是在程序crash之前,Runtime 會(huì)給我們動(dòng)態(tài)方法解析的機(jī)會(huì)秸仙,消息發(fā)送的步驟大致如下:

1.檢測(cè)這個(gè) selector 是不是要忽略的嘴拢。比如 Mac OS X 開(kāi)發(fā),有了垃圾回收就不理會(huì) retain寂纪,release 這些函數(shù)了.

2.檢測(cè)這個(gè) target 是不是 nil 對(duì)象席吴。ObjC 的特性是允許對(duì)一個(gè) nil 對(duì)象執(zhí)行任何一個(gè)方法不會(huì) Crash赌结,因?yàn)闀?huì)被忽略掉.

3.如果上面兩個(gè)都過(guò)了,那就開(kāi)始查找這個(gè)類的 IMP孝冒,先從 cache 里面找柬姚,完了找得到就跳到對(duì)應(yīng)的函數(shù)去執(zhí)行.
如果 cache 找不到就找一下方法分發(fā)表.

4.如果分發(fā)表找不到就到超類的分發(fā)表去找,一直找庄涡,直到找到NSObject類為止.

如果還找不到就要開(kāi)始進(jìn)入消息轉(zhuǎn)發(fā)了量承,消息轉(zhuǎn)發(fā)的大致過(guò)程如圖:
這里寫(xiě)圖片描述

1.進(jìn)入 resolveInstanceMethod: 方法,指定是否動(dòng)態(tài)添加方法穴店。若返回NO撕捍,則進(jìn)入下一步,若返回YES迹鹅,則通過(guò) class_addMethod 函數(shù)動(dòng)態(tài)地添加方法卦洽,消息得到處理,此流程完畢.

2.resolveInstanceMethod: 方法返回 NO 時(shí)斜棚,就會(huì)進(jìn)入 forwardingTargetForSelector: 方法,這是 Runtime 給我們的第二次機(jī)會(huì)该窗,用于指定哪個(gè)對(duì)象響應(yīng)這個(gè) selector弟蚀。返回nil,進(jìn)入下一步酗失,返回某個(gè)對(duì)象义钉,則會(huì)調(diào)用該對(duì)象的方法.

3.若 forwardingTargetForSelector: 返回的是nil,則我們首先要通過(guò) methodSignatureForSelector: 來(lái)指定方法簽名规肴,返回nil捶闸,表示不處理,若返回方法簽名拖刃,則會(huì)進(jìn)入下一步.

4當(dāng)?shù)?methodSignatureForSelector: 方法返回方法簽名后删壮,就會(huì)調(diào)用 forwardInvocation: 方法,我們可以通過(guò) anInvocation 對(duì)象做很多處理兑牡,比如修改實(shí)現(xiàn)方法央碟,修改響應(yīng)對(duì)象等.

如果到最后,消息還是沒(méi)有得到響應(yīng)均函,程序就會(huì)crash亿虽,詳細(xì)代碼在這里


Objective-C Runtime

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末苞也,一起剝皮案震驚了整個(gè)濱河市洛勉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌如迟,老刑警劉巖收毫,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡牛哺,警方通過(guò)查閱死者的電腦和手機(jī)陋气,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)引润,“玉大人巩趁,你說(shuō)我怎么就攤上這事〈靖剑” “怎么了议慰?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)奴曙。 經(jīng)常有香客問(wèn)我别凹,道長(zhǎng),這世上最難降的妖魔是什么洽糟? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任炉菲,我火速辦了婚禮,結(jié)果婚禮上坤溃,老公的妹妹穿的比我還像新娘拍霜。我一直安慰自己,他們只是感情好薪介,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布祠饺。 她就那樣靜靜地躺著,像睡著了一般汁政。 火紅的嫁衣襯著肌膚如雪道偷。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天记劈,我揣著相機(jī)與錄音勺鸦,去河邊找鬼。 笑死抠蚣,一個(gè)胖子當(dāng)著我的面吹牛祝旷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嘶窄,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼怀跛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了柄冲?” 一聲冷哼從身側(cè)響起吻谋,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎现横,沒(méi)想到半個(gè)月后漓拾,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體阁最,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年骇两,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了速种。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡低千,死狀恐怖配阵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情示血,我是刑警寧澤棋傍,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站难审,受9級(jí)特大地震影響瘫拣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜告喊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一麸拄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧黔姜,春花似錦感帅、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)岖是。三九已至帮毁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間豺撑,已是汗流浹背烈疚。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留聪轿,地道東北人爷肝。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像陆错,于是被迫代替她去往敵國(guó)和親灯抛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容