iOS底層原理總結(jié) - 探尋Runtime本質(zhì)(四)

super的本質(zhì)

Runtime-Demo

首先來(lái)看一道面試題。 下列代碼中Person繼承自NSObject贝室,Student繼承自Person契讲,寫(xiě)出下列代碼輸出內(nèi)容仿吞。

#import "Student.h"
@implementation Student
- (instancetype)init
{
    if (self = [super init]) {
        NSLog(@"[self class] = %@", [self class]);
        NSLog(@"[self superclass] = %@", [self superclass]);
        NSLog(@"----------------");
        NSLog(@"[super class] = %@", [super class]);
        NSLog(@"[super superclass] = %@", [super superclass]);

    }
    return self;
}
@end

直接來(lái)看一下打印內(nèi)容

Runtime-super[6601:1536402] [self class] = Student
Runtime-super[6601:1536402] [self superclass] = Person
Runtime-super[6601:1536402] ----------------
Runtime-super[6601:1536402] [super class] = Student
Runtime-super[6601:1536402] [super superclass] = Person

上述代碼中可以發(fā)現(xiàn)無(wú)論是self還是super調(diào)用classsuperclass的結(jié)果都是相同的。

為什么結(jié)果是相同的捡偏?super關(guān)鍵字在調(diào)用方法的時(shí)候底層調(diào)用流程是怎樣的唤冈?

我們通過(guò)一段代碼來(lái)看一下super底層實(shí)現(xiàn),為Person類(lèi)提供run方法银伟,Student類(lèi)中重寫(xiě)run方法你虹,方法內(nèi)部調(diào)用[super run];,將Student.m轉(zhuǎn)化為c++代碼查看其底層實(shí)現(xiàn)彤避。

- (void) run
{
    [super run];
    NSLog(@"Student...");
}

上述代碼轉(zhuǎn)化為c++代碼

static void _I_Student_run(Student * self, SEL _cmd) {

    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("run"));

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_jm_dztwxsdn7bvbz__xj2vlp8980000gn_T_Student_e677aa_mi_0);
}

通過(guò)上述源碼可以發(fā)現(xiàn)傅物,[super run];轉(zhuǎn)化為底層源碼內(nèi)部其實(shí)調(diào)用的是objc_msgSendSuper函數(shù)。

objc_msgSendSuper函數(shù)內(nèi)傳遞了兩個(gè)參數(shù)忠藤。__rw_objc_super結(jié)構(gòu)體和sel_registerName("run")方法名挟伙。

__rw_objc_super結(jié)構(gòu)體內(nèi)傳入的參數(shù)是selfclass_getSuperclass(objc_getClass("Student"))也就是Student的父類(lèi)Person

首先我們找到objc_msgSendSuper函數(shù)查看內(nèi)部結(jié)構(gòu)

OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

可以發(fā)現(xiàn)objc_msgSendSuper中傳入的結(jié)構(gòu)體是objc_super,我們來(lái)到objc_super內(nèi)部查看其內(nèi)部結(jié)構(gòu)模孩。 我們通過(guò)源碼查找objc_super結(jié)構(gòu)體查看其內(nèi)部結(jié)構(gòu)。

// 精簡(jiǎn)后的objc_super結(jié)構(gòu)體
struct objc_super {
    __unsafe_unretained _Nonnull id receiver; // 消息接受者
    __unsafe_unretained _Nonnull Class super_class; // 消息接受者的父類(lèi)
    /* super_class is the first class to search */ 
    // 父類(lèi)是第一個(gè)開(kāi)始查找的類(lèi)
};

objc_super結(jié)構(gòu)體中可以發(fā)現(xiàn)receiver消息接受者仍然為self贮缅,superclass僅僅是用來(lái)告知消息查找從哪一個(gè)類(lèi)開(kāi)始榨咐。從父類(lèi)的類(lèi)對(duì)象開(kāi)始去查找。

我們通過(guò)一張圖看一下其中的區(qū)別谴供。

image.png

從上圖中我們知道 super調(diào)用方法的消息接受者receiver仍然是self块茁,只是從父類(lèi)的類(lèi)對(duì)象開(kāi)始去查找方法。

那么此時(shí)重新回到面試題桂肌,我們知道class的底層實(shí)現(xiàn)如下面代碼所示

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}

class內(nèi)部實(shí)現(xiàn)是根據(jù)消息接受者返回其對(duì)應(yīng)的類(lèi)對(duì)象数焊,最終會(huì)找到基類(lèi)的方法列表中,而selfsuper的區(qū)別僅僅是self從本類(lèi)類(lèi)對(duì)象開(kāi)始查找方法崎场,super從父類(lèi)類(lèi)對(duì)象開(kāi)始查找方法佩耳,因此最終得到的結(jié)果都是相同的。

另外我們?cè)诨氐?code>run方法內(nèi)部谭跨,很明顯可以發(fā)現(xiàn)干厚,如果super不是從父類(lèi)開(kāi)始查找方法,從本類(lèi)查找方法的話螃宙,就調(diào)用方法本身造成循環(huán)調(diào)用方法而crash蛮瞄。

同理superclass底層實(shí)現(xiàn)同class類(lèi)似,其底層實(shí)現(xiàn)代碼如下入所示

+ (Class)superclass {
    return self->superclass;
}

- (Class)superclass {
    return [self class]->superclass;
}

因此得到的結(jié)果也是相同的谆扎。

objc_msgSendSuper2函數(shù)

上述OC代碼轉(zhuǎn)化為c++代碼并不能說(shuō)明super底層調(diào)用函數(shù)就一定objc_msgSendSuper挂捅。

其實(shí)super底層真正調(diào)用的函數(shù)時(shí)objc_msgSendSuper2函數(shù)我們可以通過(guò)查看super調(diào)用方法轉(zhuǎn)化為匯編代碼來(lái)驗(yàn)證這一說(shuō)法

- (void)viewDidLoad {
    [super viewDidLoad];
}

通過(guò)斷點(diǎn)查看其匯編調(diào)用棧

image.png

上圖中可以發(fā)現(xiàn)super底層其實(shí)調(diào)用的是objc_msgSendSuper2函數(shù),我們來(lái)到源碼中查找一下objc_msgSendSuper2函數(shù)的底層實(shí)現(xiàn)堂湖,我們可以在匯編文件中找到其相關(guān)底層實(shí)現(xiàn)闲先。

ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame
MESSENGER_START

ldp x0, x16, [x0]       // x0 = real receiver, x16 = class
ldr x16, [x16, #SUPERCLASS] // x16 = class->superclass
CacheLookup NORMAL

END_ENTRY _objc_msgSendSuper2

通過(guò)上面匯編代碼我們可以發(fā)現(xiàn)状土,其實(shí)底層是在函數(shù)內(nèi)部調(diào)用的class->superclass獲取父類(lèi),并不是我們上面分析的直接傳入的就是父類(lèi)對(duì)象饵蒂。

其實(shí)_objc_msgSendSuper2內(nèi)傳入的結(jié)構(gòu)體為objc_super2

struct objc_super2 {
    id receiver;
    Class current_class;
};

我們可以發(fā)現(xiàn)objc_super2中除了消息接受者receiver声诸,另一個(gè)成員變量current_class也就是當(dāng)前類(lèi)對(duì)象。

與我們上面分析的不同_objc_msgSendSuper2函數(shù)內(nèi)其實(shí)傳入的是當(dāng)前類(lèi)對(duì)象退盯,然后在函數(shù)內(nèi)部獲取當(dāng)前類(lèi)對(duì)象的父類(lèi)彼乌,并且從父類(lèi)開(kāi)始查找方法。

我們也可以通過(guò)代碼驗(yàn)證上述結(jié)構(gòu)體內(nèi)成員變量究竟是當(dāng)前類(lèi)對(duì)象還是父類(lèi)對(duì)象渊迁。下文中我們會(huì)通過(guò)另外一道面試題驗(yàn)證慰照。

isKindOfClass 與 isMemberOfClass

首先看一下isKindOfClass isKindOfClass對(duì)象方法底層實(shí)現(xiàn)

- (BOOL)isMemberOfClass:(Class)cls {
   // 直接獲取實(shí)例類(lèi)對(duì)象并判斷是否等于傳入的類(lèi)對(duì)象
    return [self class] == cls;
}

- (BOOL)isKindOfClass:(Class)cls {
   // 向上查詢,如果找到父類(lèi)對(duì)象等于傳入的類(lèi)對(duì)象則返回YES
   // 直到基類(lèi)還不相等則返回NO
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

isKindOfClass isKindOfClass類(lèi)方法底層實(shí)現(xiàn)

// 判斷元類(lèi)對(duì)象是否等于傳入的元類(lèi)元類(lèi)對(duì)象
// 此時(shí)self是類(lèi)對(duì)象 object_getClass((id)self)就是元類(lèi)
+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}

// 向上查找琉朽,判斷元類(lèi)對(duì)象是否等于傳入的元類(lèi)對(duì)象
// 如果找到基類(lèi)還不相等則返回NO
// 注意:這里會(huì)找到基類(lèi)
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

通過(guò)上述源碼分析我們可以知道毒租。 isMemberOfClass 判斷左邊是否剛好等于右邊類(lèi)型。 isKindOfClass 判斷左邊或者左邊類(lèi)型的父類(lèi)是否剛好等于右邊類(lèi)型箱叁。 注意:類(lèi)方法內(nèi)部是獲取其元類(lèi)對(duì)象進(jìn)行比較

我們查看以下代碼

NSLog(@"%d",[Person isKindOfClass: [Person class]]);
NSLog(@"%d",[Person isKindOfClass: object_getClass([Person class])]);
NSLog(@"%d",[Person isKindOfClass: [NSObject class]]);

// 輸出內(nèi)容
Runtime-super[46993:5195901] 0
Runtime-super[46993:5195901] 1
Runtime-super[46993:5195901] 1

分析上述輸出內(nèi)容: 第一個(gè) 0:上面提到過(guò)類(lèi)方法是獲取self的元類(lèi)對(duì)象與傳入的參數(shù)進(jìn)行比較墅垮,但是第一行我們傳入的是類(lèi)對(duì)象,因此返回NO耕漱。

第二個(gè) 1:同上算色,此時(shí)我們傳入Person元類(lèi)對(duì)象,此時(shí)返回YES螟够。驗(yàn)證上述說(shuō)法

第三個(gè) 1:我們發(fā)現(xiàn)此時(shí)傳入的是NSObject類(lèi)對(duì)象并不是元類(lèi)對(duì)象灾梦,但是返回的值卻是YES。 原因是基元類(lèi)的superclass指針是指向基類(lèi)對(duì)象的妓笙。如下圖13號(hào)線

image.png

那么Person元類(lèi)通過(guò)superclass指針一直找到基元類(lèi)若河,還是不相等,此時(shí)再次通過(guò)superclass指針來(lái)到基類(lèi)寞宫,那么此時(shí)發(fā)現(xiàn)相等就會(huì)返回YES了萧福。

復(fù)習(xí)

通過(guò)一道面試題對(duì)之前學(xué)習(xí)的知識(shí)進(jìn)行復(fù)習(xí)。 問(wèn):以下代碼是否可以執(zhí)行成功淆九,如果可以统锤,打印結(jié)果是什么。

// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
- (void)test;
@end

// Person.m
#import "Person.h"
@implementation Person
- (void)test
{
    NSLog(@"test print name is : %@", self.name);
}
@end

// ViewController.m
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];

    id cls = [Person class];
    void *obj = &cls;
    [(__bridge id)obj test];

    Person *person = [[Person alloc] init];
    [person test];
}

這道面試題確實(shí)很無(wú)厘頭的一道題炭庙,日常工作中沒(méi)有人這樣寫(xiě)代碼饲窿,但是需要解答這道題需要很完備的底層知識(shí),我們通過(guò)這道題來(lái)復(fù)習(xí)一下焕蹄,首先看一下打印結(jié)果逾雄。

Runtime面試題[15842:2579705] test print name is : <ViewController: 0x7f95514077a0>
Runtime面試題[15842:2579705] test print name is : (null)

通過(guò)上述打印結(jié)果我們可以看出,是可以正常運(yùn)行并打印的,說(shuō)明obj可以正常調(diào)用test方法鸦泳,但是我們發(fā)現(xiàn)打印self.name的內(nèi)容卻是<ViewController: 0x7f95514077a0>银锻。下面person實(shí)例調(diào)用test不做過(guò)多解釋了,主要用來(lái)和上面方法調(diào)用做對(duì)比做鹰。

為什么會(huì)是這樣的結(jié)果呢击纬?首先通過(guò)一張圖看一下兩種調(diào)用方法的內(nèi)存信息。

image.png

通過(guò)上圖我們可以發(fā)現(xiàn)兩種方法調(diào)用方式很相近钾麸。那么obj為什么可以正常調(diào)用方法更振?

obj為什么可以正常調(diào)用方法

首先通過(guò)之前的學(xué)習(xí)我們知道,person調(diào)用方法時(shí)首先通過(guò)isa指針找到類(lèi)對(duì)象進(jìn)而查找方法并進(jìn)行調(diào)用饭尝。

person實(shí)例對(duì)象內(nèi)實(shí)際上是取最前面8個(gè)字節(jié)空間也就是isa并通過(guò)計(jì)算得出類(lèi)對(duì)象地址肯腕。

而通過(guò)上圖我們可以發(fā)現(xiàn),obj在調(diào)用test方法時(shí)钥平,也會(huì)通過(guò)其內(nèi)存地址找到cls实撒,而cls中取出最前面8個(gè)字節(jié)空間其內(nèi)部存儲(chǔ)的剛好是Person類(lèi)對(duì)象地址。因此obj是可以正常調(diào)用方法的涉瘾。

為什么self.name打印內(nèi)容為ViewController對(duì)象

問(wèn)題出在[super viewDidLoad];這段代碼中知态,通過(guò)上述對(duì)super本質(zhì)的分析我們知道,super內(nèi)部調(diào)用objc_msgSendSuper2函數(shù)立叛。

我們知道objc_msgSendSuper2函數(shù)內(nèi)部會(huì)傳入兩個(gè)參數(shù)肴甸,objc_super2結(jié)構(gòu)體和SEL,并且objc_super2結(jié)構(gòu)體內(nèi)有兩個(gè)成員變量消息接受者和其父類(lèi)囚巴。

struct objc_super2 {
    id receiver; // 消息接受者
    Class current_class; // 當(dāng)前類(lèi)
};
};

通過(guò)以上分析我們可以得知[super viewDidLoad];內(nèi)部objc_super2結(jié)構(gòu)體內(nèi)存儲(chǔ)如下所示

struct objc_super = {
    self,
    [ViewController Class]
};

那么objc_msgSendSuper2函數(shù)調(diào)用之前,會(huì)先創(chuàng)建局部變量objc_super2結(jié)構(gòu)體用于為objc_msgSendSuper2函數(shù)傳遞的參數(shù)友扰。

局部變量由高地址向低地址分配在椡妫空間

我們知道局部變量是存儲(chǔ)在棧空間內(nèi)的村怪,并且是由高地址向低地址有序存儲(chǔ)秽浇。 我們通過(guò)一段代碼驗(yàn)證一下。

long long a = 1;
long long b = 2;
long long c = 3;
NSLog(@"%p %p %p", &a,&b,&c);
// 打印內(nèi)容
0x7ffee9774958 0x7ffee9774950 0x7ffee9774948

通過(guò)上述代碼打印內(nèi)容甚负,我們可以驗(yàn)證局部變量在椉砘溃空間內(nèi)是由高地址向低地址連續(xù)存儲(chǔ)的。

那么我們回到面試題中梭域,通過(guò)上述分析我們知道斑举,此時(shí)代碼中包含局部變量以此為objc_super2 結(jié)構(gòu)體cls病涨、obj富玷。通過(guò)一張圖展示一下這些局部變量存儲(chǔ)結(jié)構(gòu)。

image.png

上面我們知道當(dāng)person實(shí)例對(duì)象調(diào)用方法的時(shí)候,會(huì)取實(shí)例變量前8個(gè)字節(jié)空間也就是isa來(lái)找到類(lèi)對(duì)象地址赎懦。那么當(dāng)訪問(wèn)實(shí)例變量的時(shí)候雀鹃,就跳過(guò)isa的8個(gè)字節(jié)空間往下面去找實(shí)例變量。

那么當(dāng)obj在調(diào)用test方法的時(shí)候同樣找到cls中取出前8個(gè)字節(jié)励两,也就是Person類(lèi)對(duì)象的內(nèi)存地址黎茎,那么當(dāng)訪問(wèn)實(shí)例變量_name的時(shí)候,會(huì)繼續(xù)向高地址內(nèi)存空間查找当悔,此時(shí)就會(huì)找到objc_super結(jié)構(gòu)體傅瞻,從中取出8個(gè)字節(jié)空間也就是self,因此此時(shí)訪問(wèn)到的self.name就是ViewController對(duì)象先鱼。

當(dāng)訪問(wèn)成員變量_name的時(shí)候俭正,test函數(shù)中的self也就是方法調(diào)用者其實(shí)是obj,那么self.name就是通過(guò)obj去找_name焙畔,跳過(guò)cls的8個(gè)指針掸读,在取8個(gè)指針此時(shí)自然獲取到ViewController對(duì)象

因此上述代碼中cls就相當(dāng)于isa宏多,isa下面的8個(gè)字節(jié)空間就相當(dāng)于_name成員變量儿惫。因此成員變量_name的訪問(wèn)到的值就是cls地址后向高地址位取8個(gè)字節(jié)地址空間存儲(chǔ)的值。

為了驗(yàn)證上述說(shuō)法伸但,我們做一個(gè)實(shí)驗(yàn)肾请,在cls后高地址中添加一個(gè)string,那么此時(shí)cls下面的高地址位就是string更胖。以下示例代碼

- (void)viewDidLoad {
    [super viewDidLoad];

    NSString *string = @"string";

    id cls = [Person class];
    void *obj = &cls;
    [(__bridge id)obj test];

    Person *person = [[Person alloc] init];
    [person test];
}

此時(shí)的局部變量?jī)?nèi)存結(jié)構(gòu)如下圖所示

image.png

此時(shí)在訪問(wèn)_name成員變量的時(shí)候铛铁,越過(guò)cls內(nèi)存往高地址找就會(huì)來(lái)到string,此時(shí)拿到的成員變量就是string了却妨。 我們來(lái)看一下打印內(nèi)容

Runtime面試題[16887:2829028] test print name is : string
Runtime面試題[16887:2829028] test print name is : (null)

再通過(guò)一段代碼使用int數(shù)據(jù)進(jìn)行試驗(yàn)

- (void)viewDidLoad {
    [super viewDidLoad];

    int a = 3;

    id cls = [Person class];
    void *obj = &cls;
    [(__bridge id)obj test];

    Person *person = [[Person alloc] init];
    [person test];
}
// 程序crash饵逐,壞地址訪問(wèn)

我們發(fā)現(xiàn)程序因?yàn)閴牡刂吩L問(wèn)而crash,此時(shí)局部變量?jī)?nèi)存結(jié)構(gòu)如下圖所示

image.png

當(dāng)需要訪問(wèn)_name成員變量的時(shí)候彪标,會(huì)在cls后高地址為查找8位的字節(jié)空間倍权,而我們知道int占4位字節(jié),那么此時(shí)8位的內(nèi)存空間同時(shí)占據(jù)int數(shù)據(jù)及objc_super結(jié)構(gòu)體內(nèi)薄声,因此就會(huì)造成壞地址訪問(wèn)而crash题画。

我們添加新的成員變量進(jìn)行訪問(wèn)

// Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *nickName;
- (void)test;
@end
------------
// Person.m
#import "Person.h"
@implementation Person
- (void)test
{
    NSLog(@"test print name is : %@", self.nickName);
}
@end
--------
//  ViewController.m
- (void)viewDidLoad {
    [super viewDidLoad];

    NSObject *obj1 = [[NSObject alloc] init];

    id cls = [Person class];
    void *obj = &cls;
    [(__bridge id)obj test];

    Person *person = [[Person alloc] init];
    [person test];
}

我們看一下打印內(nèi)容

// 打印內(nèi)容
// Runtime面試題[17272:2914887] test print name is : <ViewController: 0x7ffc6010af50>
// Runtime面試題[17272:2914887] test print name is : (null)

可以發(fā)現(xiàn)此時(shí)打印的仍然是ViewController對(duì)象,我們先來(lái)看一下其局部變量?jī)?nèi)存結(jié)構(gòu)

image.png

首先通過(guò)obj找到cls婴程,cls找到類(lèi)對(duì)象進(jìn)行方法調(diào)用廓奕,此時(shí)在訪問(wèn)nickName時(shí),obj查找成員變量蒸绩,首先跳過(guò)8個(gè)字節(jié)的cls,之后跳過(guò)name所占的8個(gè)字節(jié)空間铃肯,最終再取8個(gè)字節(jié)空間取出其中的值作為成員變量的值患亿,那么此時(shí)也就是self了押逼。

總結(jié):這道面試題雖然很無(wú)厘頭,讓人感覺(jué)無(wú)從下手但是考察的內(nèi)容非常多咙冗。 1. super的底層本質(zhì)為調(diào)用objc_msgSendSuper2函數(shù)漂彤,傳入objc_super2結(jié)構(gòu)體,結(jié)構(gòu)體內(nèi)部存儲(chǔ)消息接受者和當(dāng)前類(lèi)挫望,用來(lái)告知系統(tǒng)方法查找從父類(lèi)開(kāi)始。

2. 局部變量分配在椛H空間蛉幸,并且從高地址向低地址連續(xù)分配。先創(chuàng)建的局部變量分配在高地址奕纫,后續(xù)創(chuàng)建的局部變量連續(xù)分配在較低地址若锁。

3. 方法調(diào)用的消息機(jī)制斧吐,通過(guò)isa指針找到類(lèi)對(duì)象進(jìn)行消息發(fā)送。

4. 指針存儲(chǔ)的是實(shí)例變量的首字節(jié)地址煤率,上述例子中person指針存儲(chǔ)的其實(shí)就是實(shí)例變量?jī)?nèi)部的isa指針的地址。

5. 訪問(wèn)成員變量的本質(zhì)洋只,找到成員變量的地址,按照成員變量所占的字節(jié)數(shù),取出地址中存儲(chǔ)的成員變量的值识虚。

驗(yàn)證objc_msgSendSuper2內(nèi)傳入的結(jié)構(gòu)體參數(shù)

我們使用以下代碼來(lái)驗(yàn)證上文中遺留的問(wèn)題

- (void)viewDidLoad {
    [super viewDidLoad];
    id cls = [Person class];
    void *obj = &cls;
    [(__bridge id)obj test];
}

上述代碼的局部變量?jī)?nèi)存結(jié)構(gòu)我們之前已經(jīng)分析過(guò)了肢扯,真正的內(nèi)存結(jié)構(gòu)應(yīng)該如下圖所示

image.png

通過(guò)上面對(duì)面試題的分析,我們現(xiàn)在想要驗(yàn)證objc_msgSendSuper2函數(shù)內(nèi)傳入的結(jié)構(gòu)體參數(shù)担锤,只需要拿到cls的地址蔚晨,然后向后移8個(gè)地址就可以獲取到objc_super結(jié)構(gòu)體內(nèi)的self肛循,在向后移8個(gè)地址就是current_class的內(nèi)存地址。通過(guò)打印current_class的內(nèi)容累舷,就可以知道傳入objc_msgSendSuper2函數(shù)內(nèi)部的是當(dāng)前類(lèi)對(duì)象還是父類(lèi)對(duì)象了夹孔。

我們來(lái)證明他是UIViewController 還是ViewController即可

image.png

通過(guò)上圖可以發(fā)現(xiàn)析蝴,最終打印的內(nèi)容確實(shí)為當(dāng)前類(lèi)對(duì)象。 因此objc_msgSendSuper2函數(shù)內(nèi)部其實(shí)傳入的是當(dāng)前類(lèi)對(duì)象尝盼,并且在函數(shù)內(nèi)部獲取其父類(lèi)佑菩,告知系統(tǒng)從父類(lèi)方法開(kāi)始查找的殿漠。

Runtime API

首先我們通過(guò)來(lái)看一段代碼,后續(xù)Runtime API的使用均基于此代碼蕾哟。

// Person類(lèi)繼承自NSObject莲蜘,包含run方法
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
- (void)run;
@end

#import "Person.h"
@implementation Person
- (void)run
{
    NSLog(@"%s",__func__);
}
@end

// Car類(lèi)繼承自NSObejct,包含run方法
#import "Car.h"
@implementation Car
- (void)run
{
    NSLog(@"%s",__func__);
}
@end

類(lèi)相關(guān)API

1\. 動(dòng)態(tài)創(chuàng)建一個(gè)類(lèi)(參數(shù):父類(lèi)逐哈,類(lèi)名问顷,額外的內(nèi)存空間)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)

2\. 注冊(cè)一個(gè)類(lèi)(要在類(lèi)注冊(cè)之前添加成員變量)
void objc_registerClassPair(Class cls) 

3\. 銷(xiāo)毀一個(gè)類(lèi)
void objc_disposeClassPair(Class cls)

示例:
void run(id self , SEL _cmd) {
    NSLog(@"%@ - %@", self,NSStringFromSelector(_cmd));
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 創(chuàng)建類(lèi) superclass:繼承自哪個(gè)類(lèi) name:類(lèi)名 size_t:格外的大小,創(chuàng)建類(lèi)是否需要擴(kuò)充空間
        // 返回一個(gè)類(lèi)對(duì)象
        Class newClass = objc_allocateClassPair([NSObject class], "Student", 0);

        // 添加成員變量 
        // cls:添加成員變量的類(lèi) name:成員變量的名字 size:占據(jù)多少字節(jié) alignment:內(nèi)存對(duì)齊算途,最好寫(xiě)1 types:類(lèi)型哗戈,int類(lèi)型就是@encode(int) 也就是i
        class_addIvar(newClass, "_age", 4, 1, @encode(int));
        class_addIvar(newClass, "_height", 4, 1, @encode(float));

        // 添加方法
        class_addMethod(newClass, @selector(run), (IMP)run, "v@:");

        // 注冊(cè)類(lèi)
        objc_registerClassPair(newClass);

        // 創(chuàng)建實(shí)例對(duì)象
        id student = [[newClass alloc] init];

        // 通過(guò)KVC訪問(wèn)
        [student setValue:@10 forKey:@"_age"];
        [student setValue:@180.5 forKey:@"_height"];

        // 獲取成員變量
        NSLog(@"_age = %@ , _height = %@",[student valueForKey:@"_age"], [student valueForKey:@"_height"]);

        // 獲取類(lèi)的占用空間
        NSLog(@"類(lèi)對(duì)象占用空間%zd", class_getInstanceSize(newClass));

        // 調(diào)用動(dòng)態(tài)添加的方法
        [student run];

    }
    return 0;
}

// 打印內(nèi)容
// Runtime應(yīng)用[25605:4723961] _age = 10 , _height = 180.5
// Runtime應(yīng)用[25605:4723961] 類(lèi)對(duì)象占用空間16
// Runtime應(yīng)用[25605:4723961] <Student: 0x10072e420> - run

注意
類(lèi)一旦注冊(cè)完畢唯咬,就相當(dāng)于類(lèi)對(duì)象和元類(lèi)對(duì)象里面的結(jié)構(gòu)就已經(jīng)創(chuàng)建好了。
因此必須在注冊(cè)類(lèi)之前狞贱,添加成員變量蜀涨。方法可以在注冊(cè)之后再添加,因?yàn)榉椒ㄊ强梢詣?dòng)態(tài)添加的氧枣。
創(chuàng)建的類(lèi)如果不需要使用了 别垮,需要釋放類(lèi)。
4\. 獲取isa指向的Class烧董,如果將類(lèi)對(duì)象傳入獲取的就是元類(lèi)對(duì)象胧奔,如果是實(shí)例對(duì)象則為類(lèi)對(duì)象
Class object_getClass(id obj)

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        NSLog(@"%p,%p,%p",object_getClass(person), [Person class],
              object_getClass([Person class]));
    }
    return 0;
}
// 打印內(nèi)容
Runtime應(yīng)用[21115:3807804] 0x100001298,0x100001298,0x100001270
5\. 設(shè)置isa指向的Class龙填,可以動(dòng)態(tài)的修改類(lèi)型。例如修改了person對(duì)象的類(lèi)型胶背,也就是說(shuō)修改了person對(duì)象的isa指針的指向喘先,中途讓對(duì)象去調(diào)用其他類(lèi)的同名方法廷粒。
Class object_setClass(id obj, Class cls)

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        [person run];

        object_setClass(person, [Car class]);
        [person run];
    }
    return 0;
}
// 打印內(nèi)容
Runtime應(yīng)用[21147:3815155] -[Person run]
Runtime應(yīng)用[21147:3815155] -[Car run]
最終其實(shí)調(diào)用了car的run方法
6\. 用于判斷一個(gè)OC對(duì)象是否為Class
BOOL object_isClass(id obj)

// 判斷OC對(duì)象是實(shí)例對(duì)象還是類(lèi)對(duì)象
NSLog(@"%d",object_isClass(person)); // 0
NSLog(@"%d",object_isClass([person class])); // 1
NSLog(@"%d",object_isClass(object_getClass([person class]))); // 1 
// 元類(lèi)對(duì)象也是特殊的類(lèi)對(duì)象
7\. 判斷一個(gè)Class是否為元類(lèi)
BOOL class_isMetaClass(Class cls)
8\. 獲取類(lèi)對(duì)象父類(lèi)
Class class_getSuperclass(Class cls)

成員變量相關(guān)API

1\. 獲取一個(gè)實(shí)例變量信息,描述信息變量的名字暇番,占用多少字節(jié)等
Ivar class_getInstanceVariable(Class cls, const char *name)

2\. 拷貝實(shí)例變量列表(最后需要調(diào)用free釋放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)

3\. 設(shè)置和獲取成員變量的值
void object_setIvar(id obj, Ivar ivar, id value)
id object_getIvar(id obj, Ivar ivar)

4\. 動(dòng)態(tài)添加成員變量(已經(jīng)注冊(cè)的類(lèi)是不能動(dòng)態(tài)添加成員變量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)

5\. 獲取成員變量的相關(guān)信息思喊,傳入成員變量信息恨课,返回C語(yǔ)言字符串
const char *ivar_getName(Ivar v)
6\. 獲取成員變量的編碼,types
const char *ivar_getTypeEncoding(Ivar v)

示例:
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 獲取成員變量的信息
        Ivar nameIvar = class_getInstanceVariable([Person class], "_name");
        // 獲取成員變量的名字和編碼
        NSLog(@"%s, %s", ivar_getName(nameIvar), ivar_getTypeEncoding(nameIvar));

        Person *person = [[Person alloc] init];
        // 設(shè)置和獲取成員變量的值
        object_setIvar(person, nameIvar, @"xx_cc");
        // 獲取成員變量的值
        object_getIvar(person, nameIvar);
        NSLog(@"%@", object_getIvar(person, nameIvar));
        NSLog(@"%@", person.name);

        // 拷貝實(shí)例變量列表
        unsigned int count ;
        Ivar *ivars = class_copyIvarList([Person class], &count);

        for (int i = 0; i < count; i ++) {
            // 取出成員變量
            Ivar ivar = ivars[i];
            NSLog(@"%s, %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
        }

        free(ivars);

    }
    return 0;
}

// 打印內(nèi)容
// Runtime應(yīng)用[25783:4778679] _name, @"NSString"
// Runtime應(yīng)用[25783:4778679] xx_cc
// Runtime應(yīng)用[25783:4778679] xx_cc
// Runtime應(yīng)用[25783:4778679] _name, @"NSString"

屬性相關(guān)AIP

1\. 獲取一個(gè)屬性
objc_property_t class_getProperty(Class cls, const char *name)

2\. 拷貝屬性列表(最后需要調(diào)用free釋放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

3\. 動(dòng)態(tài)添加屬性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                  unsigned int attributeCount)

4\. 動(dòng)態(tài)替換屬性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                      unsigned int attributeCount)

5\. 獲取屬性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)

方法相關(guān)API

1\. 獲得一個(gè)實(shí)例方法希俩、類(lèi)方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)

2\. 方法實(shí)現(xiàn)相關(guān)操作
IMP class_getMethodImplementation(Class cls, SEL name) 
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2) 

3\. 拷貝方法列表(最后需要調(diào)用free釋放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)

4\. 動(dòng)態(tài)添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

5\. 動(dòng)態(tài)替換方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)

6\. 獲取方法的相關(guān)信息(帶有copy的需要調(diào)用free去釋放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)

7\. 選擇器相關(guān)
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)

8\. 用block作為方法實(shí)現(xiàn)
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)

Runtime的應(yīng)用

關(guān)于Runtime的應(yīng)用請(qǐng)參考iOS-RunTime應(yīng)用

Runtime 博文推薦

Runtime圖解
Runtime 10種實(shí)際用法
完整總結(jié)
objc_msgSend
詳解
快速上手
消息機(jī)制
Method Swizzling開(kāi)發(fā)實(shí)例匯總
最實(shí)用的runtime總結(jié)
實(shí)際開(kāi)發(fā)中的應(yīng)用

參考文章:
https://juejin.im/post/5b4636acf265da0f7f448f0f

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鳞上,隨后出現(xiàn)的幾起案子吊档,更是在濱河造成了極大的恐慌,老刑警劉巖涡上,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拒名,死亡現(xiàn)場(chǎng)離奇詭異增显,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)同云,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)炸站,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人禁偎,你說(shuō)我怎么就攤上這事“侍矗” “怎么了盒至?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)樱衷。 經(jīng)常有香客問(wèn)我登淘,道長(zhǎng)黔州,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任牲蜀,我火速辦了婚禮绅这,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘度苔。我一直安慰自己浑度,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布甩骏。 她就那樣靜靜地躺著先慷,像睡著了一般论熙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 48,954評(píng)論 1 283
  • 那天二蓝,我揣著相機(jī)與錄音,去河邊找鬼踊跟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛箕憾,可吹牛的內(nèi)容都是我干的拳昌。 我是一名探鬼主播,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼御铃,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼上真!你這毒婦竟也來(lái)了羹膳?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤就珠,失蹤者是張志新(化名)和其女友劉穎醒颖,沒(méi)想到半個(gè)月后图贸,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡偿洁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年沟优,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了挠阁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片溯饵。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡锨用,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出啄巧,到底是詐尸還是另有隱情掌栅,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布澄耍,位于F島的核電站齐莲,受9級(jí)特大地震影響枚钓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜星掰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一嫩舟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧播玖,春花似錦饭于、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至钳榨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間营罢,已是汗流浹背饼齿。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工候醒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留杂瘸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓敌土,卻偏偏與公主長(zhǎng)得像运翼,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子矩欠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

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