ISA指針探索

1.什么是isa指針呢?

眾所眾知的每個對象里面都有一個isa指針突颊,而這個isa指針指向哪里呢荷鼠?
首先我們先來看一段代碼

@interface HFStudent : NSObject
@end

HFStudent *p = [[HFStudent alloc] init];

(lldb) x/4gx p
0x280898130: 0x000001a1021e56a1 0x0000000000000000
0x280898140: 0x0000000000000000 0x0000000000000000

(lldb) x/4gx 0x000001a1021e56a1
0x1021e56a1: 0x9800000001021e56 0x4000000001d88802
0x1021e56b1: 0x000003000281fac4 0x2400018010000000

我們知道對象的首地址指向的就是isa指針即0x000001a1021e56a1何吝,在這個地址里面都包含了什么信息呢?從上面的輸出我們看不出來

2 源碼分析isa

接下來我們通過源碼來分析看看這個isa指針到底是什么
首先我們跟蹤調(diào)試來到

initIsa(cls, true, hasCxxDtor); 

objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    isa_t newisa(0);
    if (!nonpointer) {
        newisa.setClass(cls, this);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());
#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this); 
#endif
        newisa.extra_rc = 1;
    }
    isa = newisa;
}

通過斷點調(diào)試簡化代碼如下
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    isa_t newisa(0);
    if (!nonpointer) {
        newisa.setClass(cls, this);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());
        newisa.bits = ISA_MAGIC_VALUE;
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this); 
        newisa.extra_rc = 1;
    }
    isa = newisa;
}

通過源碼得出isa的類型為isa_t ,接下來我們看看isa_t的源碼

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    uintptr_t bits;

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls;

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };

    bool isDeallocating() {
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() {
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif

    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};

從源碼中得知isa是一個聯(lián)合體
接下來我們補充一些關于聯(lián)合體和isa相關知識

3. union 聯(lián)合體

結(jié)構(gòu)體(struct)中所有變量是“共存”的——優(yōu)點是“有容乃大”点待,全面;缺點是struct內(nèi)存空間的分配是粗放的弃舒,不管用不用癞埠,全分配。
而聯(lián)合體(union)中是各變量是“互斥”的——缺點就是不夠“包容”聋呢;但優(yōu)點是內(nèi)存使用更為精細靈活苗踪,也節(jié)省了內(nèi)存空間。
接下來我們通過例子來說明

union HFUni {
    int a;
    char b;
    short c;
};

 union HFUni uni;
uni.a = 100;
NSLog(@"a=%d---b=%d---c=%d---%lu", uni.a, uni.b, uni.c, sizeof(uni));
uni.b = 'a';
NSLog(@"a=%d---b=%d---c=%d---%lu", uni.a, uni.b, uni.c, sizeof(uni));
uni.c = 200;
NSLog(@"a=%d---b=%d---c=%d---%lu", uni.a, uni.b, uni.c, sizeof(uni));

2021-06-16 17:18:34.806007+0800 StoreKitDemo[2084:455336] a=100---b=100---c=100---4
2021-06-16 17:18:34.806222+0800 StoreKitDemo[2084:455336] a=97---b=97---c=97---4
2021-06-16 17:18:37.421626+0800 StoreKitDemo[2084:455336] a=200---b=-56---c=200---4

從結(jié)果中我們看到聯(lián)合體的大小是4字節(jié)也就是聯(lián)合體里面最大類型大小削锰,這邊通過賦值也可以看到每一次賦值通铲,聯(lián)合體里面所有的變量都發(fā)生變化,這也證明了各變量是互斥的器贩。

4 isa補充

nonpointer:表示是否對 isa 指針開啟指針優(yōu)化
0:純isa指針颅夺,1:不?是類對象地址,isa 中包含了類信息、對象的引?計數(shù)等

has_assoc:關聯(lián)對象標志位蛹稍,0沒有吧黄,1存在

has_cxx_dtor:該對象是否有 C++ 或者 Objc 的析構(gòu)器,如果有析構(gòu)函數(shù),則需要做析構(gòu)邏輯,
如果沒有,則可以更快的釋放對象

shiftcls:
存儲類指針的值。開啟指針優(yōu)化的情況下稳摄,在 arm64 架構(gòu)中有 33 位?來存儲類指針稚字。

magic:?于調(diào)試器判斷當前對象是真的對象還是沒有初始化的空間

weakly_referenced:志對象是否被指向或者曾經(jīng)指向?個 ARC 的弱變量,
沒有弱引?的對象可以更快釋放

deallocating:標志對象是否正在釋放內(nèi)存

has_sidetable_rc:當對象引?技術?于 10 時厦酬,則需要借?該變量存儲進位
extra_rc:當表示該對象的引?計數(shù)值胆描,實際上是引?計數(shù)值減 1,
例如仗阅,如果對象的引?計數(shù)為 10昌讲,那么 extra_rc 為 9。如果引?計數(shù)?于 10减噪,
則需要使?到下?的 has_sidetable_rc短绸。

有了上面的知識车吹,我們知道shiftcls存放的是類的信息,至于是在什么時候存放的呢醋闭?我們看到代碼newisa.setClass(cls, this); 跟蹤進去調(diào)試看看

inline void
isa_t::setClass(Class newCls, UNUSED_WITHOUT_PTRAUTH objc_object *obj)
{
    // Match the conditional in isa.h.
#if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#   if ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_NONE
    // No signing, just use the raw pointer.
    uintptr_t signedCls = (uintptr_t)newCls;

#   elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ONLY_SWIFT
    // We're only signing Swift classes. Non-Swift classes just use
    // the raw pointer
    uintptr_t signedCls = (uintptr_t)newCls;
    if (newCls->isSwiftStable())
        signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));

#   elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ALL
    // We're signing everything
    uintptr_t signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));

#   else
#       error Unknown isa signing mode.
#   endif

    shiftcls_and_sig = signedCls >> 3;

#elif SUPPORT_INDEXED_ISA
    // Indexed isa only uses this method to set a raw pointer class.
    // Setting an indexed class is handled separately.
    cls = newCls;

#else // Nonpointer isa, no ptrauth
    shiftcls = (uintptr_t)newCls >> 3;
#endif
}

通過斷點和類內(nèi)部變化我們發(fā)現(xiàn)在指向shiftcls = (uintptr_t)newCls >> 3時


643A22C1-4FB0-4E3C-8F32-4B768C018A19.png

變化成


BD7A4085-4B5E-41E2-A188-511FD610DBC7.png

由此可見shiftcls = (uintptr_t)newCls >> 3 正是設置類關聯(lián)的代碼

x86平臺
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_HAS_CXX_DTOR_BIT 1
#   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 unused            : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8

arm64平臺
#     define ISA_MASK        0x0000000ffffffff8ULL
#     define ISA_MAGIC_MASK  0x000003f000000001ULL
#     define ISA_MAGIC_VALUE 0x000001a000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 1
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;                                       \
        uintptr_t has_assoc         : 1;                                       \
        uintptr_t has_cxx_dtor      : 1;                                       \
        uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
        uintptr_t magic             : 6;                                       \
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19

通過上面的宏窄驹,我們可以知道shiftcls在x86平臺上是從3-47,在arm64平臺上是3-37位证逻,這樣我們就可以通過代碼示例來證明
環(huán)境:iphone 8(arm64)

HFStudent *p = [[HFStudent alloc] init];
NSLog(@"%@---%p", p, p);

(lldb) x/4gx p
0x2807ec6c0: 0x000001a100e7d98d 0x0000000000000000
0x2807ec6d0: 0x0000000000000000 0x0000000000000000
(lldb) p 0x000001a100e7d98d & 0x0000000ffffffff8ULL
(unsigned long long) $1 = 4310161800
(lldb) po 0x000001a100e7d98d & 0x0000000ffffffff8ULL
HFStudent

0x000001a100e7d98d 為p的isa指針
isa指針 & 0x0000000ffffffff8ULL -> HFStudent  
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末乐埠,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子囚企,更是在濱河造成了極大的恐慌丈咐,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件龙宏,死亡現(xiàn)場離奇詭異棵逊,居然都是意外死亡,警方通過查閱死者的電腦和手機银酗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進店門辆影,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人花吟,你說我怎么就攤上這事秸歧。” “怎么了衅澈?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵键菱,是天一觀的道長。 經(jīng)常有香客問我今布,道長经备,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任部默,我火速辦了婚禮侵蒙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘傅蹂。我一直安慰自己纷闺,他們只是感情好,可當我...
    茶點故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布份蝴。 她就那樣靜靜地躺著犁功,像睡著了一般。 火紅的嫁衣襯著肌膚如雪婚夫。 梳的紋絲不亂的頭發(fā)上浸卦,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天,我揣著相機與錄音案糙,去河邊找鬼限嫌。 笑死靴庆,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的怒医。 我是一名探鬼主播炉抒,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼稚叹!你這毒婦竟也來了端礼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤入录,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后佳镜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體僚稿,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年蟀伸,在試婚紗的時候發(fā)現(xiàn)自己被綠了蚀同。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡啊掏,死狀恐怖蠢络,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情迟蜜,我是刑警寧澤刹孔,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站娜睛,受9級特大地震影響髓霞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜畦戒,卻給世界環(huán)境...
    茶點故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一方库、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧障斋,春花似錦纵潦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至晴裹,卻和暖如春被济,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背涧团。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工只磷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留经磅,地道東北人。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓钮追,卻偏偏與公主長得像预厌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子元媚,可洞房花燭夜當晚...
    茶點故事閱讀 43,562評論 2 349

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