iOS底層探索 --- OC對(duì)象原理(下)

image

今天我們來對(duì)OC對(duì)象的原理進(jìn)行最后一篇文章的分析锰霜,在這里你講了解到一下內(nèi)容:

1乓搬、對(duì)象的底層本質(zhì)

2、聯(lián)合體位域

3车份、isaClass的關(guān)系

4、isa 的Class 的賦值反過程(通過位運(yùn)算得到Class地址)

參考文章:C 位域


1牡彻、對(duì)象的底層本質(zhì)

對(duì)象在底層的本質(zhì)扫沼,實(shí)際上是一個(gè)結(jié)構(gòu)體,這一點(diǎn)我們可以用C++輔助代碼來看一下庄吼。還記不記得我們?cè)谔剿?code>Block底層原理的時(shí)候缎除,用到的指令clang -rewrite-objc XXX.m。同樣的总寻,這里我們也將Person類器罐,轉(zhuǎn)換成C++來看一下其底層到底是什么。

/******** Person.h ********/
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;

@end

NS_ASSUME_NONNULL_END


/******** Person.m ********/
#import "Person.h"

@implementation Person

@end

執(zhí)行指令clang -rewrite-objc Person.m 我們得到Person.cpp(注:在Person.cpp文件中渐行,查看Person轰坊,通常從最下面開始查找比較方便)。
我們?cè)?code>Person.cpp文件中可以看到祟印,Person這個(gè)類在底層就是一個(gè)結(jié)構(gòu)體衰倦,那么Person類所創(chuàng)建的對(duì)象,在底層的數(shù)據(jù)結(jié)構(gòu)就是一個(gè)結(jié)構(gòu)體旁理。

  • Person_IMPL
image

Person_IMPLstruct NSObject_IMPL NSObject_IVARS;相當(dāng)于結(jié)構(gòu)體的繼承樊零,通過字面意思我們也可以知道,繼承的是成員變量(Ivar)孽文,那么我們跟進(jìn)去看一下:

image

可以看到是一個(gè)isa指針驻襟,這也就意味著,在OC中芋哭,每一個(gè)對(duì)象都會(huì)有一個(gè)isa指針沉衣,因?yàn)檫@是系統(tǒng)幫我們自動(dòng)完成的。

1.1 Class

這里的Class又是什么呢减牺?其實(shí)Class就是一個(gè)結(jié)構(gòu)體指針:

image
1.2 id

大家注意上圖豌习,我們還看到

typedef struct objc_object *id;

NSObject在底層中是objc_object;看到這里的id拔疚,我突然想明白了一點(diǎn)肥隆,為什么我們?cè)趯懘a的時(shí)候,用id去修飾對(duì)象不會(huì)報(bào)錯(cuò)稚失,比如id person等等栋艳。就是因?yàn)?code>id本身就是一個(gè)指針。

1.3 set & get

Personnameset & get方法:

image

大家會(huì)發(fā)現(xiàn)句各,為什么在底層中set & get方法會(huì)有參數(shù)呢吸占?這就是我們經(jīng)常說的隱藏參數(shù)晴叨。

這里跟大家簡(jiǎn)單講一下,大家注意看OBJC_IVAR_$_Person$_name是什么矾屯?它其實(shí)是一個(gè)unsigned longextern "C" unsigned long OBJC_IVAR_$_Person$_name;
這里的取值與賦值兼蕊,都是對(duì)內(nèi)存地址的操作,拿到Person對(duì)象的首地址件蚕,偏移到name所在的位置孙技,再進(jìn)行取值 或者 賦值


2 聯(lián)合體位域

2.1 聯(lián)合體(union)

首先我們來看一下什么是聯(lián)合體:

union Teacher {
    char        *name;
    int         age;
    double      height ;
};

這就是一個(gè)聯(lián)合體骤坐。

  • 聯(lián)合體(union)的特點(diǎn)是:各個(gè)變量之間是互斥的绪杏,也就是說只能給其中一個(gè)變量賦值下愈;其優(yōu)點(diǎn)是內(nèi)存使用更為精細(xì)靈活纽绍,也節(jié)省了內(nèi)存空間。
  • 結(jié)構(gòu)體(struct)的特點(diǎn)是:各個(gè)變量之間是共存的势似,也就是說可以同時(shí)給多個(gè)變量賦值拌夏;其缺點(diǎn)是內(nèi)存空間使用更為粗放,不管用不用履因,內(nèi)存權(quán)分配障簿。

下面我們通過代碼來看一下聯(lián)合體

image


2.2 位域

舉個(gè)例子,我們有下面一個(gè)結(jié)構(gòu)體:

struct Car1 {
    BOOL front; 
    BOOL back;
    BOOL left;
    BOOL right;
};

此時(shí)Car1的大小是4個(gè)字節(jié):

image

此時(shí)就有一個(gè)問題栅迄,實(shí)際我們并不需要這么多內(nèi)存空間站故,我們只需要一個(gè)字節(jié)就可以完整的表達(dá)Car1的意思。
為了達(dá)到這個(gè)效果毅舆,這個(gè)時(shí)候我們只需要在結(jié)構(gòu)體定義的時(shí)候西篓,指定成員變量所占的二進(jìn)制位數(shù)(Bit),這就是位域:

struct Car2 {
    BOOL front;
    BOOL back : 1;
    BOOL left : 6;
    BOOL right: 4;
};

:用來限定成員變量占用的位數(shù)憋活;front沒有限制岂津,根據(jù)類型推算其站1個(gè)字節(jié)(Byte);back悦即,left吮成,right:后面的數(shù)字限制,不能根據(jù)數(shù)據(jù)類型計(jì)算長(zhǎng)度辜梳,其分別占用1(Bit)粱甫,6(Bit)4(Bit)的內(nèi)存作瞄。

image

3 isa 與 Class之間的關(guān)聯(lián)

3.1 isa_t

我們之前在分析alloc流程的時(shí)候魔种,在_class_createInstanceFromZone中有這樣一段代碼:

image

跟進(jìn)去:

inline void 
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;
        // 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
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }

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

我們通過上面的代碼會(huì)發(fā)現(xiàn):

  • isa = newisa;
  • newisaisa_t類型的粉洼。

那么我們跟進(jìn)isa_t节预,可以發(fā)現(xiàn)isa_t是一個(gè)聯(lián)合體:

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

我們通常在定義一個(gè)類對(duì)象的時(shí)候是不是這樣定義的Person *p = [[Person alloc] init]叶摄,其中對(duì)象p也叫做指針指針8字節(jié)(Byte) -- 8 * 8 = 64(bit 位)安拟,如果這64只是存儲(chǔ)一個(gè)指針地址就會(huì)產(chǎn)生浪費(fèi)蛤吓。

由于每個(gè)類都有一個(gè)isa,于是就在isa里面存儲(chǔ)了一個(gè)類相關(guān)信息糠赦,比如:是否正在釋放会傲、引用計(jì)數(shù)weak拙泽、關(guān)聯(lián)對(duì)象淌山、析構(gòu)函數(shù)等等。

isa_t是一個(gè)聯(lián)合體顾瞻,那么我們要確認(rèn)其其中存儲(chǔ)的內(nèi)存泼疑,一般來說,我們要去看位域荷荤,于是我們?cè)?code>ISA_BITFIELD中找到了這個(gè):

image

這是一個(gè)退渗,其含義如下(注:有兩個(gè)不同環(huán)境下的,下面會(huì)做介紹):

變量名 值的含義
nonpointer 表示是否對(duì)isa指針開啟指針優(yōu)化 蕴纳。0:純isa指針会油;1:不只是類對(duì)象地址,isa中包含了類信息古毛,對(duì)象的引用計(jì)數(shù)等等翻翩。
has_assoc 關(guān)聯(lián)對(duì)象標(biāo)志位 0:沒有;1:存在稻薇。
has_cxx_dtor 該對(duì)象是否有C++或者Objc的析構(gòu)器嫂冻,如果有析構(gòu)函數(shù),則需要坐析構(gòu)邏輯颖低,如果沒有絮吵,則可以更快的釋放對(duì)象。
shiftcls 存儲(chǔ)類指針的值忱屑。開啟指針優(yōu)化的情況下蹬敲,在arm64架構(gòu)中,有33位用于存儲(chǔ)類指針莺戒。
magic 用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象伴嗡,還是沒有初始化的空間。
weakly_referenced 用于表示對(duì)象是否被指向或曾將指向一個(gè)ARC的若變量从铲,沒有弱引用的對(duì)象可以更快釋放瘪校。
deallocating 標(biāo)志對(duì)象是否正在釋放內(nèi)存。
has_sidetable_rc 當(dāng)對(duì)象引用計(jì)數(shù)大于10的時(shí)候,則需要家用該變量存儲(chǔ)進(jìn)位阱扬。
extra_rc 表示該對(duì)象的引用計(jì)數(shù)數(shù)值泣懊,實(shí)際上是引用計(jì)數(shù)值減1。例如:如果對(duì)象的引用計(jì)數(shù)為10麻惶,那么extra_rc為9馍刮;如果引用計(jì)數(shù)大于10,則需要用到上面的has_sidetable_rc窃蹋。

其中x86_64arm64兩種架構(gòu)中卡啰,ISA_BITFIELD的對(duì)比如下(圖片來源isa與類關(guān)聯(lián)的原理):

image
3.2 isa與Class地址的關(guān)系

我們?cè)趺赐ㄟ^isa指針,得到對(duì)應(yīng)的類地址呢警没,注意上面有一個(gè)ISA_MASK宏匈辱,我們用isa & ISA_MASK就可以得到類地址,如下:

image

為什么要&一下呢杀迹,因?yàn)锳pple并不想讓我們直接得到對(duì)應(yīng)的值亡脸,也就是說,不想讓值直接明文暴露出來佛南。所以加了一個(gè)掩碼來配合一下梗掰。

3.3 initIsa

initIsa中嵌言,如果nonpointer == 0(純isa指針)嗅回,那就isa = isa_t((uintptr_t)cls);;否則就進(jìn)行一系列bit的賦值(位域的賦值):

image

4 isa 的Class 的賦值反過程

這里我們利用位運(yùn)算來得到我們想要的Class地址摧茴。
我們首先來回顧一下上面講到的x86_64ISA_BITFIELD

image

我們要找的就是shifcls绵载;而shifcls右邊是3個(gè)bit,左邊是17個(gè)bit苛白。那么我們先右移3位娃豹,再左移20位,最后右移17位就可以得到shifcls购裙。

具體操作如下:


image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末懂版,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子躏率,更是在濱河造成了極大的恐慌躯畴,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件薇芝,死亡現(xiàn)場(chǎng)離奇詭異蓬抄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)夯到,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門嚷缭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事阅爽÷沸遥” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵付翁,是天一觀的道長(zhǎng)劝赔。 經(jīng)常有香客問我,道長(zhǎng)胆敞,這世上最難降的妖魔是什么着帽? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮移层,結(jié)果婚禮上仍翰,老公的妹妹穿的比我還像新娘。我一直安慰自己观话,他們只是感情好予借,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著频蛔,像睡著了一般灵迫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上晦溪,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天瀑粥,我揣著相機(jī)與錄音,去河邊找鬼三圆。 笑死狞换,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的舟肉。 我是一名探鬼主播修噪,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼路媚!你這毒婦竟也來了黄琼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤整慎,失蹤者是張志新(化名)和其女友劉穎脏款,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體院领,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡弛矛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年们童,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了政勃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡宫纬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出万俗,到底是詐尸還是另有隱情湾笛,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布闰歪,位于F島的核電站嚎研,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏库倘。R本人自食惡果不足惜临扮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望教翩。 院中可真熱鬧杆勇,春花似錦、人聲如沸饱亿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽彪笼。三九已至钻注,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間配猫,已是汗流浹背幅恋。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留章姓,地道東北人佳遣。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓识埋,卻偏偏與公主長(zhǎng)得像凡伊,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子窒舟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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