iOS的OC的isa的底層原理

前言

一步一個腳印地探索iOS的OC底層原理坛猪,通過前面的文章可以大概了解了OC對象創(chuàng)建的alloc原理
OC對象的內存字節(jié)對齊
最楷,但是這也只是知道了對象創(chuàng)建的底層過程和開辟內存空間的,這篇文章將介紹對象的本質和對象與類的關聯(lián)---isa

1.isa的初始化

isa指針:在Objective-C中拙寡,任何類的定義都是對象庐橙。類和類的實例(對象)沒有任何本質上的區(qū)別。任何對象都有isa指針怯伊。也就是說在對象創(chuàng)建的時候就會有isa指針初始化了真慢。為了搞清楚還是需要用到OC對象創(chuàng)建的alloc原理里面源碼的_class_createInstanceFromZone的方法的部分源碼包各,然后跟著流程進去得到如下的部分源碼

//_class_createInstanceFromZone的部分代碼
//初始化實例的isa指針
 obj->initInstanceIsa(cls, hasCxxDtor);
 
 inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    assert(!cls->instancesRequireRawIsa());
    assert(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    assert(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa.cls = cls;
    } else {
        assert(!DisableNonpointerIsa);
        assert(!cls->instancesRequireRawIsa());

        isa_t newisa(0);

#if SUPPORT_INDEXED_ISA
        assert(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif

        // This write must be performed in a single store in some cases
        // (for example when realizing a class because other threads
        // may simultaneously try to use the class).
        // fixme use atomics here to guarantee single-store and to
        // guarantee memory order w.r.t. the class index table
        // ...but not too atomic because we don't want to hurt instantiation
        isa = newisa;
    }
}

其中的nonpointer字面的意思是沒有指針的,一般情況下nonpointer是為true的,只有在例如實現(xiàn)了allocwithzone方法胎挎,retain,release等的時候會是false始赎。如果為false是直接將傳進來的cls為isa的關聯(lián)的cls賦值邦泄。

其他的剩下的部分就是對isa的初始化賦值了。但是具體的isa內部是怎樣的還是不知道的砸彬,從源碼中isa_t點擊進去可以查看颠毙。

2.isa的內部結構

通過源碼可以知道isaisa_t類型的內部結構

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

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

#ifndef _UINTPTR_T
#define _UINTPTR_T
typedef unsigned long           uintptr_t;
#endif /* _UINTPTR_T */

從中可以知道,isa是一個聯(lián)合體(union),里面有關聯(lián)的類cls和long型的bits砂碉。

什么是聯(lián)合體(union)呢蛀蜜?聯(lián)合體是一種特殊的類,也是一種構造類型的數(shù)據(jù)結構增蹭。完全就是共用一個內存首地址滴某,并且各種變量名都可以同時使用,操作也是共同生效沪铭。所以也叫共用體壮池。并且聯(lián)合體(union)中是各變量是“互斥”的,但是內存使用更為精細靈活杀怠,也節(jié)省了內存空間椰憋。

由上面的概念可以知道,clsbits之間是互斥的赔退,即有cls就沒有bits,有bits就沒有cls橙依。這就很好地解釋了為什么上面的源碼在初始化isa的時候會用nonpointer來區(qū)分開证舟。所以isa的大小占8個字節(jié),64位窗骑。其中這64位中分別存儲了什么呢女责?通過ISA_BITFIELD位域源碼:

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   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 deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   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

這兩種是分別在arm64x86系統(tǒng)架構下的,但是都是64位的创译,本文的說明是在x86下介紹的抵知。

  • nonpointer:占1位,表示是否對isa指針開啟指針優(yōu)化软族,0:表示純指針刷喜;1:表示不止是類對象地址,isa中包含了類信息立砸、對象的引用計數(shù)等掖疮。
  • has_assoc:占1位,表示關聯(lián)對象標志位颗祝,0沒有浊闪,1存在。
  • has_cxx_dtor:占1位螺戳,表示該對象是否有C++或者Objc的析構器搁宾,如果有析構函數(shù),則需要做析構邏輯温峭,如果沒有則可以更快的釋放對象猛铅。
  • shiftcls:占44位,存儲類指針的值凤藏。開啟指針優(yōu)化的情況下,在arm64架構中有33位用來存儲類指針堕伪。(由上面的初始化源碼也可以很好的說明,關聯(lián)的類指針向右移3位)
newisa.shiftcls = (uintptr_t)cls >> 3;
  • magic:占6位揖庄,用于調試器判斷當前對象是真的對象還是沒有初始化的空間。
  • weakly_referenced:占1位欠雌,表示對象是否被指向或者曾經(jīng)指向一個ARC的弱變量蹄梢,沒有弱引用的對象可以更快釋放。
  • deallocating:占1位富俄,表示對象是否正在釋放內存禁炒。
  • has_sidetable_rc:占1位,表示當對象引用技術大于10時霍比,則需要借用該變量存儲進位幕袱。
  • extra_rc:占8位,表示該對象的引用計數(shù)值悠瞬,實際上是引用計數(shù)總值減1们豌。例如涯捻,如果對象的引用計數(shù)為10,那么extra_rc為9望迎,如果引用計數(shù)大于10障癌,則需要使用到下面的has_sidetable_rc。

3.isa是對象與類的連接

為了方便介紹下面的內容需要定義一個什么屬性都沒有的TestJason類辩尊,然后通過objc4-756.2蘋果官方的源碼涛浙。通過object_getClass這個方法可以獲取到類。

TestJason *test2 = [TestJason alloc];
Class testClass = object_getClass(test2);
NSLog(@"%@",test2);

通過源碼找到object_getClass的方法

/***********************************************************************
* object_getClass.
* Locking: None. If you add locking, tell gdb (rdar://7516456).
**********************************************************************/
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

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

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
}

#   define SUPPORT_INDEXED_ISA 0
#   define ISA_MASK        0x00007ffffffffff8ULL

從源碼中可以知道返回的isa最終是(Class)(isa.bits & ISA_MASK)摄欲。
其中源碼有一個判斷isTaggedPointer(),其中蘋果對于Tagged Pointer的概念引入轿亮,是為了節(jié)省內存和提高執(zhí)行效率,對于 64 位程序蒿涎,相關邏輯能減少一半的內存占用哀托,以及 3 倍的訪問速度提升,100 倍的創(chuàng)建劳秋、銷毀速度提升仓手。如果想了解這部分的內容可以看看深入理解 Tagged Pointer

下面就是用lldb的指令來驗證一下的玻淑。首先用x/4gx test2來打印出地址

image

然后用p/x TestJason.class打印出TestJason類的內存值

image

由源碼知道類Class的最終返回是(Class)(isa.bits & ISA_MASK)的嗽冒,所以將x/4gx test2打印出來的0x001d800100001749 & ISA_MASK的值得到如下:

image

最終發(fā)現(xiàn)$3$4的內存值是一樣的,所以isa是關聯(lián)著對象與類的补履。由前面的文章知道由于內存的優(yōu)化對象的其他屬性的位置實際會發(fā)生變化的添坊,所以對象的第一個屬性就是isa

4.isa的走位原理

通過上面的介紹箫锤,可以知道了isa是關聯(lián)著對象與類的贬蛙,并且對象的isa指向類,因為萬物皆對象谚攒,那么類的isa指向的是誰呢阳准?可以通過蘋果官方的isa的走位流程圖
isa流程圖.png

image

其中虛線是代表isa的走位,實線代表的是繼承關系的走位馏臭。圖中有一個meta class元類的概念野蝇。

什么是元類?在OC中括儒,對象的方法并沒有存儲于對象的結構體中(如果每一個對象都保存了自己能執(zhí)行的方法绕沈,那么對內存的占用有極大的影響)。
當對象的實例方法被調用時帮寻,它通過自己的isa來查找對應的類乍狐,然后在所屬類的 class_data_bits_t結構體中查找對應方法的實現(xiàn)。同時规婆,每一個objc_class 也有一個指向自己的父類的指針superclass用來查找繼承的方法澜躺。
而當調用 類方法 時蝉稳,它的查找流程是怎樣的呢?對此OC的解決方案就是引入元類掘鄙,來保證類方法也能通過相同的機制查找到耘戚。對于元類的解釋轉自OC源碼分析之isa

是不是看著這張圖的各種箭頭指向一臉懵逼呢?下面就是來對這張圖的isa的走位驗證一下操漠。

4.1 isa的走位

還是用TestJason這個什么屬性都沒有的類來介紹收津。通過lldb的指令來驗證。由上面的圖可以知道浊伙,對象的isa指向類撞秋,類的isa指向元類。

image

由圖可以知道嚣鄙,先用x/4gx test2來打印出isa的內存值吻贿,然后用isa的內存值&ISA_MASK得到$9,然后po $9此時得到的是類。然后再用x/4gx來打印$9的值哑子,此時就是相當于打印出類的isa的內存值了舅列。最后可以看到兩個TestJason,但是這兩個的內存值是不一樣的卧蜓,分別是類和元類帐要。但是中可以看到元類里面還有isa值,那么就繼續(xù)打印弥奸。

image

從中可以看到榨惠,元類的isa指向了NSObject,但是這個NSObject到底是類呢盛霎?還是元類呢赠橙?為了搞清楚,可以用x/4gx NSObject.class來打印類的isa來驗證愤炸。

image

從中可以看到$13打印的內存值是等于$16的简烤,那么就可以知道元類isa指向的是根元類isa。那么根元類isa指向誰呢摇幻?繼續(xù)打印

image

從中可以看到根元類isa指向了它的本身,這就形成了一個閉環(huán)挥萌。所以整體來說的話绰姻,就是

對象的isa-->類的isa-->元類的isa-->根元類的isa-->根元類的isa(根元類本身)

這就很好地驗證了蘋果的官網(wǎng)的isa走位圖。

4.2 對象引瀑,類之間的繼承的走位

為了介紹方便狂芋,添加多一個TestSuperJason的類,并且讓TestJason是繼承TestSuperJason的憨栽。還是通過lldb的指令來的打印class_getSuperclass方法帜矾。

這是類的繼承關系的打印

image

這是元類的繼承關系打印

image

所以它們的繼承關系是

類:
TestJason-->TestSuperJason-->NSObject-->nil
元類:
TestJason元類-->TestSuperJason元類-->NSObject元類-->NSObject類-->nil

5.對象的本質

通過上面的知識可以大概了解了isa的原理翼虫,但是對象的本質是什么還不是很了解的,可以通過clang編譯成cpp文件來查看屡萤。實現(xiàn)的代碼如下:

@interface Jason : NSObject{
    NSString *nickName;
}
@property (nonatomic, copy) NSString *name;
@end

@implementation Jason

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"123");
    }
    return 0;
}

在文件的路徑下珍剑,可以終端輸入命令,就可以查看main.cpp文件

clang -rewrite-objc main.m -o main.cpp

從中可以看到

#ifndef _REWRITER_typedef_Jason
#define _REWRITER_typedef_Jason
typedef struct objc_object Jason;
typedef struct {} _objc_exc_Jason;
#endif

extern "C" unsigned long OBJC_IVAR_$_Jason$_name;
struct Jason_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSString *nickName;
    NSString *_name;
};

// @property (nonatomic, copy) NSString *name;
/* @end */


// @implementation Jason


static NSString * _I_Jason_name(Jason * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Jason$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_Jason_setName_(Jason * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Jason, _name), (id)name, 0, 1); }
// @end


struct NSObject_IMPL {
    Class isa;
};

從中可以看到,對象最終會被編譯成結構體struct,NSObject_IMPL里面包含著isa,在類里面定義的屬性name和成員變量nickName也是在Jason_IMPL結構體里面死陆,但是屬性變量name是有getter方法_I_Jason_namesetter方法_I_Jason_setName_的招拙,而成員變量nickName是沒有的。并且這些方法里面都有默認帶有兩個參數(shù)id selfSEL _cmd,這樣就很好解釋了我們在方法中可以直接調用self措译。

5.最后

通過上面的內容可以了解到isa是一個isa_t類型的聯(lián)合體(union),并且里面的屬性是互斥的别凤,isa的大小占8字節(jié)。一般情況下都是在bits下的位域來存儲內容领虹,其中ISA_BITFIELDx86arm64架構下都是64位规哪,但是里面的屬性的占位有點區(qū)別的。isa是關聯(lián)著對象與類的塌衰,并且對象的本質就是一個結構體诉稍。至此有關isa的原理介紹到此結束。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末猾蒂,一起剝皮案震驚了整個濱河市均唉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肚菠,老刑警劉巖舔箭,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蚊逢,居然都是意外死亡层扶,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門烙荷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來镜会,“玉大人,你說我怎么就攤上這事终抽〈帘恚” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵昼伴,是天一觀的道長匾旭。 經(jīng)常有香客問我,道長圃郊,這世上最難降的妖魔是什么价涝? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮持舆,結果婚禮上色瘩,老公的妹妹穿的比我還像新娘伪窖。我一直安慰自己,他們只是感情好居兆,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布覆山。 她就那樣靜靜地躺著,像睡著了一般史辙。 火紅的嫁衣襯著肌膚如雪汹买。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天聊倔,我揣著相機與錄音晦毙,去河邊找鬼。 笑死耙蔑,一個胖子當著我的面吹牛见妒,可吹牛的內容都是我干的。 我是一名探鬼主播甸陌,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼须揣,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了钱豁?” 一聲冷哼從身側響起耻卡,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎牲尺,沒想到半個月后卵酪,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡谤碳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年溃卡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜒简。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡瘸羡,死狀恐怖,靈堂內的尸體忽然破棺而出搓茬,到底是詐尸還是另有隱情犹赖,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布卷仑,位于F島的核電站冷尉,受9級特大地震影響,放射性物質發(fā)生泄漏系枪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一磕谅、第九天 我趴在偏房一處隱蔽的房頂上張望私爷。 院中可真熱鬧雾棺,春花似錦、人聲如沸衬浑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽工秩。三九已至尸饺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間助币,已是汗流浹背浪听。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留眉菱,地道東北人迹栓。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像俭缓,于是被迫代替她去往敵國和親克伊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344