iOS底層探究-isa的初始化&走位分析

本文可能篇幅比較長,主要為了自己日后溫習知識所用。如果有幸被你發現這篇文章,并且引起了你的閱讀興趣,希望這篇文章能對你有所幫助。如發現任何有誤之處,肯請留言糾正,謝謝。?

一:isa是什么?

iOS底層探究-淺談alloc,init,new 前面的文章中我們提到過,基類NSObject有個默認的屬性isa:

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

那么isa是什么呢?
蘋果有段官方的描述: “A pointer to the class definition of which this object is an instance”。
isa其實是指一個實例對象指向該對象的類的指針。沒錯,早期的時候isa的確是單純的一個指針,只不過后期蘋果進行了優化,不但保存了指針的地址,另外也存儲了一些類的信息。下面我們看下蘋果isa的源代碼:

union isa_t {
    //兩個默認的構造函數
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

  //isa指向的類
    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        //位域
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

可以看出isa其實是個union聯合體,蘋果這塊用聯合體優化內存占用,并且使用位域ISA_BITFIELD增加可讀性。聯合體和位域的結合使用,我會單獨去寫,請持續關注,我們先看下 ISA_BITFIELD 位域的定義和使用。
定義:

#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8

根據Union聯合體的特性 isa_t 總共占用8個字節64位

其中:
nonpointer代表是否是純指針 0 代表純指針 1 代表不止是類對象地址,isa 中包含了類信息、對象的引用計數等
has_assoc
關聯對象標志位
has_cxx_dtor
該對象是否有 C++ 或者 Objc 的析構器,如果有析構函數,則需要做析構邏輯, 如果沒有,則可以更快的釋放對象
shiftcls
存儲類指針的值。開啟指針優化的情況下,在 arm64 架構中有 33 位用來存儲類指針
magic
用于調試器判斷當前對象是真的對象還是沒有初始化的空間
weakly_referenced
存儲對象是否被指向或者曾經指向一個 ARC 的弱變量,
沒有弱引用的對象可以更快釋放。
deallocating
標志對象是否正在釋放內存

has_sidetable_rc
當對象引用技術大于 10 時,則需要借用該變量存儲進位
extra_rc
當表示該對象的引用計數值,實際上是引用計數值減 1, 例如,如果對象的引用計數為 10,那么 extra_rc 為 9。如果引用計數大于 10, 則需要使用到下面的 has_sidetable_rc

二:isa指針走位

前面我們已經看到了isa_t結構體保存了類的很多信息,其中就包括了類指針的值。下面我們就研究下isa的指針走位。先看一張大神畫的圖:

isa流程圖

圖中實線箭頭代表類繼承的走向,而虛線箭頭是代表isa的走向

其實我第一次看這張圖的時候是懵逼的,亂七八糟的線根本看不出是表達什么邏輯。后來隨著對底層實現的了解和探索,才慢慢了解了圖作者想表達的意思,下面我就和大家一起來研究一下

1:靜態分析,我們要研究isa,先看下系統是怎樣獲取類對象的:

object_getClass()是系統獲取類對象的一個函數,內部是這樣實現的

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


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];
    }
}

我們知道objc都是!isTaggedPointer的,可以直接定位到ISA()函數。

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
}

SUPPORT_INDEXED_ISA這個宏定義在iOS是0.

(Class)(isa.bits & ISA_MASK)這句話就是我們的重點了,系統通過&運算結合mask來得到isa類指針的值
最后(Class)做了步強轉返回Class

2:動態調試,模擬靜態代碼的步驟,通過lldb工具印證我們的推測

我們通過objc最新的源碼,建立一個target方便我們調試。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
      
        JSPerson *object = [JSPerson alloc];
        // 斷點位置
    }
    return 0;
}
  • isa從對象指向類

添加斷點,我們通過lldb來調試

(lldb) x/2xg object // 打印類的信息
0x101a4c8b0: 0x001d800100001101 0x0000000000000000

其中0x001d800100001101就是對象中的isa我們坐下運算

(lldb) p/x 0x001d800100001101 & 0x0000000ffffffff8
(long) $2 = 0x0000000100001100

然后對對象所屬的類進行打印

(lldb) p/x object.class
(Class) $3 = 0x0000000100001100 JSPerson

我們看到了$2$3是相等的,也就印證了我們的第一步,對象和類是靠isa建立綁定的

那么類的isa會指向何處呢?我們先看下底層對類結構體的定義:

struct objc_class : objc_object // 類在底層其實是個繼承于objc_object的結構體,萬物皆對象在這里得到印證

// 我們看下objc_class這個結構體
struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

說明objc_class第一個成員也是isa,那我們繼續打印

  • isa從類指向元類
(lldb) x/2xg 0x0000000100001100
0x100001100: 0x001d8001000010d9 0x0000000100b00140

(lldb) p/x 0x001d8001000010d9 & 0x0000000ffffffff8
(long) $4 = 0x00000001000010d8

(lldb) po 0x00000001000010d8
JSPerson

我們看到輸出的又是JSPerson,但是內存地址卻不相同,其實他是JSPerson的元類(metaClass),metaClass 是 Class 對象的類,同樣也是個對象。每個類都必須有一個唯一的 metaClass.

  • isa從元類指向根元類
(lldb) x/2xg 0x00000001000010d8
0x1000010d8: 0x001d800100b000f1 0x0000000100b000f0

(lldb) p/x 0x001d800100b000f1 & 0x0000000ffffffff8
(long) $6 = 0x0000000100b000f0

(lldb) po 0x0000000100b000f0
NSObject

輸出了NSObject的元類也就是所有類的根元類

  • isa從根元類指向根根元類
(lldb)  x/2xg 0x0000000100b000f0
0x100b000f0: 0x001d800100b000f1 0x0000000100b00140

(lldb) p/x 0x001d800100b000f1 & 0x0000000ffffffff8
(long) $8 = 0x0000000100b000f0

(lldb) po 0x0000000100b000f0
NSObject

你會發現無論你打印多少次得到的結果都是NSObject內存地址也不會變,形成了循環。

三:總結

我們在把這張圖拿過來,現在感覺是不是清晰了很多?

isa流程圖

isa走位總結:實例對象->類->元類->根元類->根根元類(根元類本身)
繼承關系 :Subclass->Superclass->Root class

下一篇文章 將對類的結構做跟細致的研究,如果覺得喜歡的話還請加個關注,我會經常保持更新。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容