iOS開發(fā)之runtime(2):淺析NSObject對象的Class

logo

本系列博客是本人的源碼閱讀筆記,如果有 iOS 開發(fā)者在看 runtime 的,歡迎大家多多交流。為了方便討論,本人新建了一個微信群(iOS技術(shù)討論群),想要加入的,請?zhí)砑颖救宋⑿牛簔hujinhui207407,【加我前請備注:ios 】,本人博客http://www.kyson.cn 也在不停的更新中,歡迎一起討論

本文完整版詳見筆者小專欄:https://xiaozhuanlan.com/runtime

前言

NSObject對象是iOS開發(fā)者都很熟悉的對象,它幾乎是所有對象的根類。在任何.m文件中輸入以下代碼:

NSObject 

點(diǎn)擊NSObject跳轉(zhuǎn)到其定義文件,發(fā)現(xiàn)如下聲明:

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

其中#pragma clang diagnostic push用于去除警告,因此,我們能發(fā)現(xiàn)NSObject對象只有一個Class類型的成員變量:isa。
那么:

  • 什么是isa
  • 什么是Class類型,與class 方法有何區(qū)別

這篇文章將要給大家揭曉該問題。

我們知道任何一個類都有 class方法比如:

[NSObject class];

當(dāng)然還有superclass方法:

[NSObject superclass];

更多和clas相關(guān)的方法列舉如下:

- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;

這兩個方法相信大家都不陌生,只不過這兩個方法位于@property NSObject中,但NSObject中還是有其實(shí)現(xiàn)的。所以我們有理由相信,NSObject中的成員變量isa是有特殊含義的,點(diǎn)擊改成員變量的類型Class我們可以看到其定義:

typedef struct objc_class *Class;

繼續(xù)點(diǎn)擊objc_class

struct objc_class : objc_object {
//這里省略成員變量以及方法...
}

再次點(diǎn)擊objc_object

struct objc_object {
private:
    isa_t isa;
//這里省略成員變量以及方法...
}

層次有點(diǎn)深,但大家只關(guān)注其結(jié)構(gòu)即可:

Class本質(zhì)是一個結(jié)構(gòu)體。

關(guān)于結(jié)構(gòu)體,大家應(yīng)該都有所了解,這里再做個復(fù)習(xí)吧:
C語言和C++都支持結(jié)構(gòu)體,只是C++的結(jié)構(gòu)體基本上和類沒有區(qū)別。以下是摘自知乎某答主:

結(jié)構(gòu)體和類的區(qū)別
本質(zhì)上來說結(jié)構(gòu)體與類是同一個東西,可是默認(rèn)情況下基于可讀性的原因還是加一些區(qū)分:
結(jié)構(gòu)體就只含數(shù)據(jù)成員和構(gòu)造函數(shù)、析構(gòu)函數(shù),盡可能保持簡單。
類則包含更多的非構(gòu)造、析構(gòu)成員函數(shù),概念更大,用來描述普遍意義上的對象類型。

我們有理由相信:NSObject對象的各個方法,基本上是針對其結(jié)構(gòu)體isa對象的操作。這里我們研究幾個我們常用的方法:


本文完整版詳見筆者小專欄:https://xiaozhuanlan.com/runtime


isMemberOfClass:

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

很簡單,判斷一下,當(dāng)前的class方法是否等于參數(shù)。
因?yàn)?code>self是NSObject對象,因此我們查看class方法:

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

點(diǎn)擊object_getClass查看其定義:

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

繼續(xù)進(jìn)入方法getIsa:

inline Class 
objc_object::getIsa() 
{
    if (!isTaggedPointer()) return ISA();

    uintptr_t ptr = (uintptr_t)this;
    if (isExtTaggedPointer()) {
        uintptr_t slot = 
            (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
        return objc_tag_ext_classes[slot];
    } else {
        uintptr_t slot = 
            (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
        return objc_tag_classes[slot];
    }
}

可以發(fā)現(xiàn),越牽扯越深,閱讀有點(diǎn)困難了。但大家別著急,我們可以屏蔽
if (!isTaggedPointer()) return ISA();
以下的代碼,關(guān)于什么是TaggedPointer,筆者會在后面的文章中分析給大家。因此上面的代碼可以先簡化成:

inline Class 
objc_object::getIsa() 
{
    if (!isTaggedPointer()) return ISA();
}

繼續(xù)研究ISA()方法:

inline Class 
objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

同樣去掉暫時不需要我們理解的部分,簡化代碼如下:

inline Class 
objc_object::ISA() 
{
    return (Class)(isa.bits & ISA_MASK);
}

至此,我們可以看到class方法最終獲取的即是:結(jié)構(gòu)體objc_objectisa.bits & ISA_MASK的結(jié)果。
那,大家的疑問也會隨之而來:

  • inline 關(guān)鍵字作用,為何這里的幾個方法實(shí)現(xiàn)都在.h文件中
  • 在方法:objc_object::ISA() 中雙冒號的作用。
  • objc_object 中的isa又是什么
  • isa.bits & ISA_MASK 的含義

inline關(guān)鍵字

用來定義一個類的內(nèi)聯(lián)函數(shù),引入它的主要原因是用它替代C中表達(dá)式形式的宏定義。

也就是說,用inline關(guān)鍵字修飾的是內(nèi)聯(lián)函數(shù),內(nèi)聯(lián)函數(shù)用于替代宏定義。取代宏定義的原因是:

  1. C中使用define這種形式宏定義的原因是因?yàn)椋珻語言是一個效率很高的語言,這種宏定義在形式及使用上像一個函數(shù),但它使用預(yù)處理器實(shí)現(xiàn),沒有了參數(shù)壓棧,代碼生成等一系列的操作,因此,效率很高,這是它在C中被使用的一個主要原因。
  2. 這種宏定義在形式上類似于一個函數(shù),但在使用它時,僅僅只是做預(yù)處理器符號表中的簡單替換,因此它不能進(jìn)行參數(shù)有效性的檢測,也就不能享受C++編譯器嚴(yán)格類型檢查的好處,另外它的返回值也不能被強(qiáng)制轉(zhuǎn)換為可轉(zhuǎn)換的合適的類型,這樣,它的使用就存在著一系列的隱患和局限性。
  3. 在C++中引入了類及類的訪問控制,這樣,如果一個操作或者說一個表達(dá)式涉及到類的保護(hù)成員或私有成員,你就不可能使用這種宏定義來實(shí)現(xiàn)(因?yàn)闊o法將this指針放在合適的位置)。
  4. inline 推出的目的,也正是為了取代這種表達(dá)式形式的宏定義,它消除了宏定義的缺點(diǎn),同時又很好地繼承了宏定義的優(yōu)點(diǎn)。

雙冒號

用于表示“域操作符”,例:聲明了一個類A,類A里聲明了一個成員函數(shù)void f(),但沒有在類的聲明里給出f的定義,那么在類外定義f時,就要寫成void A::f(),表示這個f()函數(shù)是類A的成員函數(shù)。

objc_object 中的isa

之前我們已經(jīng)寫了objc_object的定義,可以知道,isa其實(shí)是一個isa_t的對象,那isa_t是什么呢,我們繼續(xù)看一下它的實(shí)現(xiàn):

union isa_t 
{
//這里省略很多變量
}

可以知道,isa_t是個聯(lián)合體,也就是說:objc_object 中的isa其實(shí)是個結(jié)構(gòu)體

isa.bits & ISA_MASK 含義

上面我們知道,isa是個聯(lián)合體,其內(nèi)部的屬性bits呢?

union isa_t 
{
   //省略部分方法和屬性...
    uintptr_t bits;

然后看uintptr_t實(shí)現(xiàn):

typedef unsigned long       uintptr_t;

發(fā)現(xiàn)其是個unsigned long類型,而ISA_MASK的定義如下:

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# else
#   error unknown architecture for packed isa
# endif

可知,其實(shí)ISA_MASK還是個數(shù)值類型。也就是說判斷兩個對象是否是同一個class其實(shí)是通過比對objc_object中的數(shù)值計算后得出的結(jié)果是否相等得出的。

講完了 isMemberOfClass方法,isKindOfClass方法這里就不多做介紹了,給出其源代碼即可:

isKindOfClass:

其實(shí)現(xiàn)如下:

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

總結(jié)

  • Class類型本質(zhì)是個結(jié)構(gòu)體,該結(jié)構(gòu)體中存儲了該NSObject中的所有信息。
  • 比對兩個類是否是同一個類,其實(shí)是判斷Class中的某個數(shù)值運(yùn)算的結(jié)果是否相等。

本文完整版詳見筆者小專欄:https://xiaozhuanlan.com/runtime

廣告

我的首款個人開發(fā)的APP壁紙寶貝上線了,歡迎大家下載。

壁紙寶貝

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

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

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