iOS底層探索之類結(jié)構(gòu)下篇

上篇分析類結(jié)構(gòu)蜡感,獲取到了屬性列表 property_array_t 和方法列表 method_array_t呻待。

實(shí)踐代碼:

@interface GLPerson : NSObject

@property (nonatomic, strong) NSString *name; /**<  8個(gè)字節(jié)  */
@property (nonatomic, strong) NSString *nickName; /**<  8個(gè)字節(jié)  */

- (void)sayHello;

+ (void)goSchool;

@end

@interface GLStudent : GLPerson

@end

一、類的類方法存儲(chǔ)

通過源碼查看趁怔,沒有看到底層把 method 區(qū)分類方法還是實(shí)例方法湿硝。既然實(shí)例方法存在類對(duì)象中薪前,類方法是不是可能存在元類中。試著通過 lldb 打印一下关斜。

(lldb) x/4gx GLPerson.class
0x1000022b8: 0x0000000100002290 0x0000000100333140
0x1000022c8: 0x000000010113f640 0x0001802400000003
(lldb) p 0x0000000100002290 & 0x00007ffffffffff8ULL // 通過isa找到元類
(unsigned long long) $1 = 4294976144
(lldb) x/4gx $1  // 讀取元類的內(nèi)存
0x100002290: 0x00000001003330f0 0x00000001003330f0
0x1000022a0: 0x0000000100643250 0x0001e03500000007
(lldb) p (class_data_bits_t *)0x1000022b0 // 通過偏移32字節(jié)獲取class_data_bits_t指針
(class_data_bits_t *) $2 = 0x00000001000022b0
(lldb) p *$2->data() // 調(diào)用class_data_bits_t 的data方法獲取class_rw_t
(class_rw_t) $3 = {
  flags = 2684878849
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = 4294975528
  }
  firstSubclass = 0x00000001000022e0
  nextSiblingClass = 0x00007fff88bf1948
}
(lldb) p $3.methods() // 打印元類的方法列表
(const method_array_t) $4 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100002070
      arrayAndFlag = 4294975600
    }
  }
}
(lldb) p $4.list
(method_list_t *const) $5 = 0x0000000100002070
(lldb) p *$5
(method_list_t) $6 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "goSchool"
      types = 0x0000000100000f7c "v16@0:8"
      imp = 0x0000000100000d70 (`+[GLPerson goSchool])
    }
  }
}
(lldb) p $6.get(0) // 第一個(gè)就是聲明的類方法
(method_t) $7 = {
  name = "goSchool"
  types = 0x0000000100000f7c "v16@0:8"
  imp = 0x0000000100000d70 (`+[GLPerson goSchool])
}
(lldb) p $6.get(1)  // 只有一個(gè)類方法示括,下一個(gè)越界了
Assertion failed: (i < count), function get, file /Users/xulong/Desktop/學(xué)習(xí)/alloc流程分析/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.

通過上面代碼發(fā)現(xiàn):

  • 方法存儲(chǔ)的時(shí)候是沒有明確的區(qū)分是類方法還是實(shí)例方法的
  • 實(shí)例方法存儲(chǔ)在類的數(shù)據(jù)中痢畜;
  • 類方法存儲(chǔ)在元類的數(shù)據(jù)中例诀;
1.1 通過runtime源碼分析

runtime 里面有方法:

  • class_getInstanceMethod : 返回一個(gè)類的指定實(shí)例方法;
  • class_getClassMethod :返回一個(gè)類的指定類方法裁着;
        const char *className = class_getName([GLPerson class]);
        Class metaClass = objc_getMetaClass(className);
        
        Method instanceM1 = class_getInstanceMethod([GLPerson class], @selector(sayHello));
        Method instanceM2 = class_getInstanceMethod([GLPerson class], @selector(goSchool));

        Method classM1 = class_getClassMethod([GLPerson class], @selector(sayHello));
        Method classM2 = class_getClassMethod([GLPerson class], @selector(goSchool));
           
        Method metaInstanceM1 = class_getInstanceMethod(metaClass, @selector(sayHello));
        Method metaInstanceM2 = class_getInstanceMethod(metaClass, @selector(goSchool));
        
        Method metaClassM1 = class_getClassMethod(metaClass, @selector(sayHello));
        Method metaClassM2 = class_getClassMethod(metaClass, @selector(goSchool));
        
        
        NSLog(@"instanceM1:%p; instanceM2:%p; classM1:%p; classM2:%p;", instanceM1, instanceM2, classM1, classM2);
        
        NSLog(@"metaInstanceM1:%p; metaInstanceM2:%p; metaClassM1:%p; metaClassM2:%p;", metaInstanceM1, metaInstanceM2, metaClassM1, metaClassM2);

console: instanceM1:0x1000020f8; instanceM2:0x0; classM1:0x0; classM2:0x100002090;
console: metaInstanceM1:0x0; metaInstanceM2:0x1000020a0; metaClassM1:0x0; metaClassM2:0x1000020a0;

分析一下
第一行類輸出:代表類有無

  • instanceM1 有值: 有 sayHello 實(shí)例方法繁涂,instanceM2 沒值: 沒有 goSchool 實(shí)例方法;
  • classM1 沒值:沒有 sayHello 類方法二驰,classM2 有值:有 goSchool 類方法扔罪;

第二行元類輸出:代表元類有無

  • metaInstanceM1 沒值: 沒有 sayHello 實(shí)例方法,metaInstanceM2 有值: 有 goSchool 實(shí)例方法桶雀;
  • metaClassM1 沒值:沒有 sayHello 類方法矿酵,metaClassM2 有值:有 goSchool 類方法;

這里先看下 class_getClassMethod 的源碼:

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}

---
    // NOT identical to this->ISA when this is a metaclass
    Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }

class_getClassMethod 的流程:

  1. class_getClassMethod 里面調(diào)用的也是 class_getInstanceMethod;
  2. 只不過傳的類是 cls->getMeta() 這個(gè)方法返回的;
  3. getMeta 如果是元類矗积,返回的就是自己全肮;
  4. getMeta 如果不是元類,返回的是 ISA();
  5. 類的 ISA() 返回的是 該類的元類;

class_getClassMethod總結(jié)

  • 如果傳的類棘捣,里面是獲取的元類的 class_getInstanceMethod 方法辜腺。也既類方法是從元類里面去找的。
  • 如果傳的是元類乍恐,直接用元類調(diào)用 class_getInstanceMethod 方法评疗。

二、是不是這個(gè)類 isKindOfClass & isMemberOfClass

首先有這樣一段代碼:

        BOOL obClass_k = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
        BOOL obClass_m = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
        BOOL peClass_k = [(id)[GLPerson class] isKindOfClass:[GLPerson class]];       //
        BOOL peClass_m = [(id)[GLPerson class] isMemberOfClass:[GLPerson class]];     //
        NSLog(@"\n obClass_k :%hhd\n obClass_m :%hhd\n peClass_k :%hhd\n peClass_m :%hhd\n",obClass_k, obClass_m, peClass_k, peClass_m);

        BOOL obInstance_k = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       //
        BOOL obInstance_m = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     //
        BOOL peInstance_k = [(id)[GLPerson alloc] isKindOfClass:[GLPerson class]];       //
        BOOL peInstance_m = [(id)[GLPerson alloc] isMemberOfClass:[GLPerson class]];     //
        NSLog(@"\n obInstance_k :%hhd\n obInstance_m :%hhd\n peInstance_k :%hhd\n peInstance_m :%hhd\n",obInstance_k, obInstance_m, peInstance_k, peInstance_m);

console:

 obClass_k :1
 obClass_m :0
 peClass_k :0
 peClass_m :0
---
 obInstance_k :1
 obInstance_m :1
 peInstance_k :1
 peInstance_m :1

可找到上面調(diào)用的具體源碼如下:

// 類方法調(diào)用
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}
// 實(shí)例方法調(diào)用
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

但是在實(shí)際調(diào)用的時(shí)候茵烈,斷點(diǎn)發(fā)現(xiàn) isKindOfClass 沒有走到 上面的源碼中百匆,通過匯編查看下具體走的什么方法

isKindOfClass匯編查看.png

發(fā)現(xiàn)調(diào)用的是objc_opt_isKindOfClasswhat !!!呜投,猜測應(yīng)該是蘋果再 llvm 編譯時(shí)期做了操作加匈。

// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
    if (slowpath(!obj)) return NO;
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        for (Class tcls = cls; tcls; tcls = tcls->superclass) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
  • 分析1 [(id)[NSObject class] isKindOfClass:[NSObject class]];

    1. [NSObject class] 返回類自己;
    2. objc_opt_isKindOfClass 中,objNSObject 類仑荐, otherClassNSObject 類雕拼;
    3. Class cls 首先賦值為 NSObject 的元類;
    4. Class tcls = clstcls 賦值元類释漆,判斷 tcls 是否等于 otherClass悲没,類不等于元類;
    5. tcls = tcls->superclassNSObject 元類的 superclass 為根類示姿,也就是 [NSObject class]甜橱;
    6. tcls 現(xiàn)在為 NSObject 類,所以和 otherClass 相等栈戳;
    7. 返回 YES岂傲;
  • 分析2 [(id)[NSObject class] isMemberOfClass:[NSObject class]]:

    1. [NSObject class] 返回類自己,所以會(huì)調(diào)用類方法;
    2. selfcls 都為 NSObject 類子檀;
    3. self->ISA() 返回 NSObject 的元類镊掖;
    4. NSObject 的元類和類不相等;
    5. 返回 NO褂痰;
  • 分析3 [(id)[GLPerson class] isKindOfClass:[GLPerson class]]:

    1. [GLPerson class] 返回類自己亩进,所以會(huì)調(diào)用類方法;
    2. objc_opt_isKindOfClass 中,objGLPerson 類缩歪, otherClassGLPerson 類归薛;
    3. Class clsClass tcls 首先賦值為 GLPerson 的元類;
    4. tcls 存在,判斷 tcls 是否等于 otherClass匪蝙,類不等于元類主籍;
    5. tcls = tcls->superclassGLPerson 元類的 superclass 為根元類逛球,也就是 [NSObject class] 的元類千元;
    6. tcls 現(xiàn)在為 NSObject 元類,不等于 otherClassGLPerson 類颤绕;
    7. 再次把 tcls = tcls->superclass幸海,NSObject 元類的 superclass[NSObject class] 類;
    8. tclsNSObject 類 不等于 otherClassGLPerson 類屋厘;
    9. 再再次 tcls = tcls->superclass涕烧,NSObject 類的 superclassnil
    10. tclsnil;
    11. 返回 NO汗洒;
  • 分析4 [(id)[GLPerson class] isMemberOfClass:[GLPerson class]]:

    1. [GLPerson class] 返回類自己,所以會(huì)調(diào)用類方法;
    2. selfcls 都為 GLPerson 類父款;
    3. self->ISA() 返回 GLPerson 的元類溢谤;
    4. GLPerson 的元類和類不相等;
    5. 返回 NO憨攒;
  • 分析5 [(id)[NSObject alloc] isKindOfClass:[NSObject class]]:

    1. [NSObject alloc] 是實(shí)例世杀;
    2. Class cls = obj->getIsa()cls 賦值為 NSObject 類肝集;
    3. Class clstcls 賦值為 NSObject 類瞻坝,otherClass 賦值為 NSObject 類;cls[NSObject class]杏瞻;
    4. 所以 tcls 等于 otherClass所刀,都是 NSObject 類衙荐;
    5. 返回 YES;
  • 分析6 [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]:

    1. [NSObject alloc] 是實(shí)例,所以會(huì)調(diào)用實(shí)例方法浮创;
    2. [self class] 為 NSObject 類忧吟,cls[NSObject class]
    3. 都是 NSObject 類斩披;
    4. 返回 YES;
  • 分析7 [(id)[GLPerson alloc] isKindOfClass:[GLPerson class]]:

    1. [GLPerson alloc] 是實(shí)例溜族;
    2. Class cls = obj->getIsa()cls 賦值為 GLPerson 類垦沉;
    3. Class clstcls 賦值為 GLPerson 類煌抒,otherClassGLPerson 類;
    4. 所以 tcls 等于 otherClass厕倍,都是 GLPerson 類摧玫;
    5. 返回 YES;
  • 分析8 [(id)[GLPerson alloc] isMemberOfClass:[GLPerson class]]:

    1. [GLPerson alloc] 是實(shí)例,所以會(huì)調(diào)用實(shí)例方法绑青;
    2. [self class] 為 GLPerson 類诬像,cls[GLPerson class]
    3. 都是 GLPerson 類闸婴;
    4. 返回 YES;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坏挠,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子邪乍,更是在濱河造成了極大的恐慌降狠,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庇楞,死亡現(xiàn)場離奇詭異榜配,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)吕晌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門蛋褥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人睛驳,你說我怎么就攤上這事烙心。” “怎么了乏沸?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵淫茵,是天一觀的道長。 經(jīng)常有香客問我蹬跃,道長匙瘪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮丹喻,結(jié)果婚禮上薄货,老公的妹妹穿的比我還像新娘。我一直安慰自己驻啤,他們只是感情好菲驴,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著骑冗,像睡著了一般赊瞬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贼涩,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天巧涧,我揣著相機(jī)與錄音,去河邊找鬼遥倦。 笑死谤绳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的袒哥。 我是一名探鬼主播缩筛,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼堡称!你這毒婦竟也來了瞎抛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤却紧,失蹤者是張志新(化名)和其女友劉穎桐臊,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晓殊,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡断凶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了巫俺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片认烁。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖识藤,靈堂內(nèi)的尸體忽然破棺而出砚著,到底是詐尸還是另有隱情,我是刑警寧澤痴昧,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站冠王,受9級(jí)特大地震影響赶撰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一豪娜、第九天 我趴在偏房一處隱蔽的房頂上張望餐胀。 院中可真熱鬧,春花似錦瘤载、人聲如沸否灾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽墨技。三九已至,卻和暖如春挎狸,著一層夾襖步出監(jiān)牢的瞬間扣汪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國打工锨匆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留崭别,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓恐锣,卻偏偏與公主長得像茅主,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子土榴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359