03 - 對象之宿,聯(lián)合體和isa探索

OC對象本質(zhì)探索

相信大家都聽說過OC對象的本質(zhì) 其實就是 結(jié)構(gòu)體逊拍,但是大多數(shù)開發(fā)者不太清楚它的底層實現(xiàn),接下來我們就探索一下:

Clang

  • clang是一個由Apple主導編寫尺借,基于LLVMC/C++/OC編譯器
  • 主要是用于底層編譯,將一些文件輸出成c++文件精拟,例如main.m輸出成main.cpp燎斩,其目的是為了更好的觀察底層的一些結(jié)構(gòu)及實現(xiàn)的邏輯,方便理解底層原理蜂绎。

接下來我們對OC對象本質(zhì)的探索就需要使用Clang編譯器栅表。

  • 首先我們創(chuàng)建一個Mac Comman Line Tool 空工程


    Comman Line Tool
  • main.m中自定義一個類`LPerson ,并添加一個屬性name
@interface LPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation LPerson
@end
  • 接下來便需要使用Clangmain.m編譯成main.cpp,命令如下,這里我們使用第一種:
//1、將 main.m 編譯成 main.cpp
clang -rewrite-objc main.m -o main.cpp

//2师枣、將 ViewController.m 編譯成  ViewController.cpp
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk ViewController.m

//以下兩種方式是通過指定架構(gòu)模式的命令行怪瓶,使用xcode工具 xcrun
//3、模擬器文件編譯
- xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 

//4践美、真機文件編譯
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp 

xcode安裝的時候順帶安裝了xcrun命令洗贰,xcrun命令在clang的基礎上進行了 一些封裝找岖,要更好用一些

使用第一種這里可能會報錯'UIKit/UIKit.h' file not found,這時改用第二種命令就可以了

編譯報錯

  • 打開編譯好的main.cpp,找到LPerson的定義哆姻,發(fā)現(xiàn)LPerson在底層會被編譯成 struct 結(jié)構(gòu)體.
LPerson 結(jié)構(gòu)體

由此可見LPerson對象在C++底層是結(jié)構(gòu)體;

  • 由上面的LPerson結(jié)構(gòu)體中我們還發(fā)現(xiàn),其中還有個屬性NSObject_IMPL,從代碼中我們可以看出,它就是isa,是繼承自NSObject宣增,屬于偽繼承,偽繼承的方式是直接將NSObject結(jié)構(gòu)體定義為LGPerson中的第一個屬性矛缨,意味著LGPerson 擁有NSObject中的所有成員變量`爹脾。
    NSObject_IMPL
NSObject源碼
NSObject源碼

從上面源碼也可以看出對象就是這樣一個結(jié)構(gòu)體,且被typedef為id 箕昭。在Foundation層id就表示一個對象 灵妨。此時便能證明,OC對象的本質(zhì) 其實就是 結(jié)構(gòu)體!

  • 從上面源碼中我們也可以看到,LGPerson中的第一個屬性 NSObject_IVARS 等效于 NSObject中的 isa,是否記得我們用lldb命令po 一個對象(LPerson)的時候的返回的是<LGPerson: 0x101404a30>,這里便是它的isa.

關(guān)于isa

通過上面的分析我們可以了解到,在對象結(jié)構(gòu)體內(nèi),有一個Class類型的 isa 變量落竹,他是一個指向該對象類型的指針 ,在面向?qū)ο缶幊讨忻诨簦瑢ο笫怯深悇?chuàng)建的,對象可以通過isa 變量找到自己所屬的類述召。

Class探索

下面是Class的源碼:

Class結(jié)構(gòu)

Class源碼中我們可以看到它的結(jié)構(gòu),類里面有個 super_class 朱转,指向了類的父類; 同時類也有一個 isa指針积暖,那類的 isa 指針指向了哪里呢藤为?

對象是按照 所定義的各個屬性和方法生產(chǎn)的,類 作為對象的模板夺刑,也可看成是對象缅疟。正如工廠里面的模子也是要專門制作模子的機器生產(chǎn)。元類 (meta class)就是設計遍愿、管理類(class)的模板存淫。對象是 類 的實例,類是 元類 的實例沼填。

所以類的isa指針指向了 元類桅咆。

如果按照這個規(guī)則,聰明的你應該想到了問題了: 元類也是對象坞笙,元類對象中也有isa轧邪,那么元類的 isa 又指向哪里呢?總不能指向元元類吧……這樣是無窮無盡的羞海。

Objective-C語言的設計者已經(jīng)考慮到了這個問題忌愚,所有元類的isa 都指向 根元類(meta Root Class)。關(guān)于實例對象却邓、類硕糊、元類之間的關(guān)系,蘋果官方給了一張圖,非常清晰的表明了三者的關(guān)系简十。

isa
  • 實線是 super_class 指針檬某,虛線是isa 指針。
  • Root class(class) 通常是 NSObject螟蝙,NSObject 是沒有超類的恢恼,所以 Root class(class)的 superclass 指向 nil
  • 每個Class都有一個 isa指針指向唯一的 Meta class
  • Root class(meta)的 superclass 指向 Root class(class)胰默,也就是 NSObject场斑,形成一個回路。
  • 每個 Meta class 的 isa 指針都指向 Root class(meta)牵署。

一個對象 可以通過isa找到類漏隐,根據(jù)類的isasuper_class 找到 元類父類 ,進而直到 根元類根類 奴迅,所以 對于最開始的例子LPerson *p = [[LPerson alloc] init]; ``LPerson可以調(diào)用NSObject的方法青责,在這中間isa 起到至關(guān)重要的作用。

由此我們可以得出結(jié)論:

  • Object-C 是基于類的對象系統(tǒng)取具。每一個對象都是一些類的實例脖隶;這個對象的isa 指針指向它所屬的類。

    • 該類描述這個 對象的數(shù)據(jù)信息 :內(nèi)存分配大小(allocation size)和實例變量的類型(ivar types )與布局(layout)暇检;
    • 也描述了 對象的行為 :它能夠響應的選擇器(selectors)和它實現(xiàn)的實例方法(instance methods)产阱。
  • 每個 Object-C 類也是一個對象,它的 isa 指針指向元類占哟,元類是關(guān)于類對象的描述,就像類是普通實例對象的描述一樣酿矢。

  • 一個元類根元類的實例榨乎;根元類是它自身的實例

  • isa 指針鏈以一個環(huán)結(jié)束:實例指向類-指向元類-指向根元類-到自身瘫筐。元類的 isa 指針并不重要蜜暑,因為在現(xiàn)實世界中,沒人會向元類對象發(fā)送消息策肝。

isa_t

isa_t最初出現(xiàn)在之前的alloc探索中,通過alloc --> _objc_rootAlloc --> callAlloc --> _objc_rootAllocWithZone --> _class_createInstanceFromZone-->initInstanceIsa方法路徑肛捍,查找到initIsa

initIsa

從源碼中可以看出,isa_t是通過聯(lián)合體(union)定義的

isa_t

isa_t類型使用聯(lián)合體的原因也是基于內(nèi)存優(yōu)化的考慮,這里的內(nèi)存優(yōu)化是指在isa指針中通過char + 位域(即二進制中每一位均可表示不同的信息)的原理實現(xiàn)之众。通常來說拙毫,isa指針占用的內(nèi)存大小是8字節(jié),即64位棺禾,已經(jīng)足夠存儲很多的信息了缀蹄,這樣可以極大的節(jié)省內(nèi)存,以提高性能

從isa_t的定義中可以看出:

  • 提供了兩個成員,clsbits缺前,由聯(lián)合體的定義所知蛀醉,這兩個成員是互斥的,也就意味著衅码,當初始化isa指針時拯刁,有兩種初始化方式

    • 通過cls初始化,bits無默認值

    • 通過bits初始化逝段,cls有默認值

  • 還提供了一個結(jié)構(gòu)體定義的位域垛玻,用于存儲類信息及其他信息,結(jié)構(gòu)體的成員ISA_BITFIELD惹恃,這是一個宏定義夭谤,有兩個版本 arm64(對應ios 移動端) 和 x86_64(對應macOS),以下是它們的一些宏定義巫糙,如下圖所示

位域的宏定義
  • nonpointer有兩個值朗儒,表示自定義的類等,占1位

    • 0純isa指針
    • 1:不只是類對象地址参淹,isa中包含了類信息醉锄、對象的引用計數(shù)
  • has_assoc 表示關(guān)聯(lián)對象標志位,占1

  • 0沒有關(guān)聯(lián)對象

  • 1存在關(guān)聯(lián)對象

  • has_cxx_dtor 表示該對象是否有C++/OC的析構(gòu)器(類似于dealloc)浙值,占1位

    • 如果有析構(gòu)函數(shù)恳不,則需要做析構(gòu)邏輯
    • 如果沒有,則可以更快的釋放對象
  • shiftclx表示存儲類的指針的值(類的地址)开呐, 即類信息

    • arm64中占 33位烟勋,開啟指針優(yōu)化的情況下,在arm64架構(gòu)中有33位用來存儲類指針
    • x86_64中占 44
  • magic 用于調(diào)試器判斷當前對象是真的對象 還是 沒有初始化的空間筐付,占6

  • weakly_refrenced是 指對象是否被指向 或者 曾經(jīng)指向一個ARC的弱變量

    • 沒有弱引用的對象可以更快釋放
  • deallocating 標志對象是是否正在釋放內(nèi)存

  • has_sidetable_rc表示 當對象引用計數(shù)大于10時卵惦,則需要借用該變量存儲進位

  • extra_rc(額外的引用計數(shù)) --- 表示該對象的引用計數(shù)值,實際上是引用計數(shù)值減1

    • 如果對象的引用計數(shù)為10瓦戚,那么extra_rc為9
isa存儲情況
isa指針 位域算法

這里是macOS環(huán)境沮尿,所以是x86_64

  • 通過main中的LGPerson 斷點 --> initInstanceIsa --> initIsa --> 走到else中的 isa初始化,執(zhí)行lldb命令:p newisa,得到newisa的詳細信息

    lldb調(diào)試

  • 然后繼續(xù)往下執(zhí)行较解,走到newisa.bits = ISA_MAGIC_VALUE;下一行畜疾,表示為isabits成員賦值,重新執(zhí)行lldb命令p newisa印衔,得到的結(jié)果如下

    newisa.bits賦值之后

我們發(fā)現(xiàn)啡捶,newsize的信息發(fā)生了變化:


newsizes數(shù)據(jù)幻化
  • 其中magic59是由于將isa指針地址轉(zhuǎn)換為二進制,從47(因為前面有4個位域奸焙,共占用47位届慈,地址是從0開始)位開始讀取6位徒溪,再轉(zhuǎn)換為十進制,如下圖所示
    magic是59

聯(lián)合體(union)

構(gòu)造數(shù)據(jù)類型的方式有以下兩種:

  • 結(jié)構(gòu)體struct
  • 聯(lián)合體union金顿,也稱為共用體
結(jié)構(gòu)體

結(jié)構(gòu)體是指把不同的數(shù)據(jù)組合成一個整體臊泌,其變量是共存的,變量不管是否使用揍拆,都會分配內(nèi)存渠概。

  • 缺點:所有屬性都分配內(nèi)存,比較浪費內(nèi)存嫂拴,假設有4個int成員播揪,一共分配了16字節(jié)(128位)的內(nèi)存,但是在使用時筒狠,你只使用了4字節(jié)(32位)猪狈,剩余的12字節(jié)(96位)就是屬于內(nèi)存的浪費

  • 優(yōu)點:存儲容量較大包容性強辩恼,且成員之間不會相互影響

聯(lián)合體

聯(lián)合體也是由不同的數(shù)據(jù)類型組成雇庙,但其變量是互斥的,所有的成員共占一段內(nèi)存灶伊。而且共用體采用了內(nèi)存覆蓋技術(shù)疆前,同一時刻只能保存一個成員的值,如果對新的成員賦值聘萨,就會將原來成員的值覆蓋掉

  • 缺點:包容性弱

  • 優(yōu)點:所有成員共用一段內(nèi)存竹椒,使內(nèi)存的使用更為精細靈活,同時也節(jié)省了內(nèi)存空間

兩者的區(qū)別

  • 內(nèi)存占用情況

    • 結(jié)構(gòu)體的各個成員會占用不同的內(nèi)存米辐,互相之間沒有影響
    • 共用體的所有成員占用同一段內(nèi)存胸完,修改一個成員會影響其余所有成員
  • 內(nèi)存分配大小

    • 結(jié)構(gòu)體內(nèi)存>=所有成員占用的內(nèi)存總和(成員之間可能會有縫隙)
    • 共用體占用的內(nèi)存等于最大的成員占用的內(nèi)存
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市翘贮,隨后出現(xiàn)的幾起案子赊窥,更是在濱河造成了極大的恐慌,老刑警劉巖择膝,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件誓琼,死亡現(xiàn)場離奇詭異检激,居然都是意外死亡肴捉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門叔收,熙熙樓的掌柜王于貴愁眉苦臉地迎上來齿穗,“玉大人,你說我怎么就攤上這事饺律∏砸常” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長脖卖。 經(jīng)常有香客問我乒省,道長,這世上最難降的妖魔是什么畦木? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任袖扛,我火速辦了婚禮,結(jié)果婚禮上十籍,老公的妹妹穿的比我還像新娘蛆封。我一直安慰自己,他們只是感情好勾栗,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布惨篱。 她就那樣靜靜地躺著,像睡著了一般围俘。 火紅的嫁衣襯著肌膚如雪砸讳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天楷拳,我揣著相機與錄音绣夺,去河邊找鬼。 笑死欢揖,一個胖子當著我的面吹牛陶耍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播她混,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼烈钞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了坤按?” 一聲冷哼從身側(cè)響起毯欣,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎臭脓,沒想到半個月后酗钞,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡来累,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年砚作,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嘹锁。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡葫录,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出领猾,到底是詐尸還是另有隱情米同,我是刑警寧澤骇扇,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站面粮,受9級特大地震影響少孝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜熬苍,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一韭山、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧冷溃,春花似錦钱磅、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至凿歼,卻和暖如春褪迟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背答憔。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工味赃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人虐拓。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓心俗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蓉驹。 傳聞我的和親對象是個殘疾皇子城榛,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353