Runtime之isa詳解

isa 概念

isa是相當(dāng)于是OC對象的一個標(biāo)識指針螟够,只要是OC對象就一定會有isa指針,arm64之前isa就是一個指向?qū)ο蠡蛘哳惖闹羔樁严康觯赼rm64之后發(fā)生了一些改進妓笙,isa在arm64之后變成了一個共用體(union)結(jié)構(gòu),同時使用位域的思想來實現(xiàn)能岩,達到節(jié)省內(nèi)存的作用寞宫;從源碼中,我們可以看到一個OC對象的isa指針并不是直接指向類對象或者元類對象的拉鹃,而是要通過一個&ISA_MASK才能獲取到真正的類對象或者元類對象辈赋,這個是為什么呢?在分析之前膏燕,我們有必要先弄懂一下共用體的相關(guān)概念和用法钥屈;

共用體概念

在進行某些算法的C語言編程的時候,需要使幾種不同類型的變量存放到同一段內(nèi)存單元中坝辫。也就是使用覆蓋技術(shù)篷就,幾個變量互相覆蓋。這種幾個不同的變量共同占用一段內(nèi)存的結(jié)構(gòu)近忙,在C語言中竭业,被稱作“共用體”類型結(jié)構(gòu),簡稱共用體及舍。

尋找真理

現(xiàn)在假設(shè)一個Person對象有3個布爾類型變量tall未辆,rich,handsome锯玛,那么如果我們平時定義的時候咐柜,肯定都是通過屬性變量來定義,例如下圖:

代碼圖

這個時候你通過終端打印更振,可以看到結(jié)果輸出是16而不是(isa指針 = 8) + (BOOL tall = 1) + (BOOL rich = 1) + (BOOL handsome = 1) = 13炕桨,這個為什么是16,前面應(yīng)該也講過了肯腕,是內(nèi)存對齊原則的原因丛忆,分配的內(nèi)存小于16的都會直接返回16;這個時候共同體就發(fā)揮作用了拉盾,因為共用體中變量可以相互覆蓋,可以使幾個不同的變量存放到同一段內(nèi)存單元中涉瘾,這樣可以很大程度上節(jié)省內(nèi)存空間,眾所周知BOOL值只有兩種情況0或者1捷兰,但是卻占據(jù)了一個字節(jié)的內(nèi)存空間立叛,而一個內(nèi)存空間有8個二進制位(0,1組成的)贡茅,于是可以嘗試用一個二進制方式來代替bool變量秘蛇,我們可以定義占用一個字節(jié)的char類型來存儲3個bool值:

char 聲明圖

我們可以在Person對象中直接初始化這個_tallRichHandsome 的值

初始化

我們可以_tallRichHandsome的后三位分別為其賦值0或者1來代表tall、rich顶考、handsome

tall赁还,rich,handsome對應(yīng)圖

這里只需要搞懂是如何賦值和取值的就可以了驹沿,這里我們先看看賦值操作先

二進制的賦值操作

前面說過BOOL變量只有0和1艘策,那么賦值操作就是只要將對應(yīng)的位置設(shè)置為0或者1就可以了,賦值為1呢渊季,我們使用 | (按位或)操作朋蔫,因為按位或的做法是只要有一個為1,結(jié)果為1却汉,所以這里我們?nèi)绻雽⒛骋晃毁x值為1的話驯妄,就將原來的值和相應(yīng)的掩碼進行按位或的操作就可以了,例如現(xiàn)在我想將tall賦值為1

根據(jù)前面的tall病涨,rich富玷,handsome的圖,我們這里需要將第三位賦值為1既穆,如下圖所示

tall賦值操作

那如果想將某一位賦值為0的話赎懦,需要將對應(yīng)的掩碼按位取反(~:按位取反符號),之后再與原本的_tallRichHandsome進行按位與操作幻工,例如下面所示:

handsome賦值為0

那么Person類中的set方法就可以按照下面這樣設(shè)置:

set方法

這個時候我們再來看看二進制的取值励两;

二進制的取值操作

取值操作我們使用按位& 操作來實現(xiàn),相同為1不同為0

取值操作

從圖中的展示結(jié)果可以看到這樣的取值操作是沒有問題的囊颅,所以我們的get方法實現(xiàn)應(yīng)該如下:

get方法實現(xiàn)

#define SLThinMask (1<<3)

#define SLTallMask (1<<2)

#define SLRichMask (1<<1)

#define SLHandsomeMask (1<<0)

上面中用到的源碼分別如下当悔,<< 代表左移符號,1 << 0,代表1左移0 也就是000000001踢代,1<<1(00000010),1<<2(00000100)

接下來我們來驗證一下測試結(jié)果是否正確

測試源碼
沒有加上!!

從結(jié)果中可以看到盲憎,這個結(jié)果不是我們想要的,我們不能這樣返回一個BOOL變量值的胳挎,因為這個值返回去有可能是一個大于1的整數(shù)饼疙,所以這里我們采取來兩個!!符號來實現(xiàn),如果&的結(jié)果之后是0慕爬,那窑眯!0 代表1屏积,!1代表0磅甩,正確炊林,如果&結(jié)果返回1,卷要!1是0渣聚,!0是1却妨,返回結(jié)果也爭取饵逐,所以上面的get方法更改成如下:

get方法糾正

雖然測試結(jié)果是正確的,但是代碼具有一定的局限性彪标,就是可讀性差,如果需要多添加幾個元素掷豺,就得重復(fù)上述的操作捞烟,這個時候我們可以考慮用位域來操作

位域

位域聲明:位域:位域長度

位域形式大概如下:

位域

set get方法實現(xiàn)如下:

set get方法

測試代碼和結(jié)果如下:

測試結(jié)果圖

發(fā)現(xiàn)結(jié)果居然驚現(xiàn)-1,但是log打斷點顯示賦值已經(jīng)是正確的了

終端調(diào)試圖

上面計算機那里的07 可以看到后面3為都是111当船,說明已經(jīng)是賦值成功的了题画,為什么直接拿07,不拿多其他多個字節(jié)呢德频,是因為_tallRichHandsome只占據(jù)一個內(nèi)存空間苍息,也就是一個字節(jié),可能會有人疑問為什么是1個字節(jié)而不是三個字節(jié)壹置,這個應(yīng)該就是位域的性質(zhì)了竞思,看下圖吧:

結(jié)構(gòu)體
位域

上面的結(jié)果都能顯示我們的賦值操作是正確的,那么出錯的可能就應(yīng)該是取值的時候操作有無導(dǎo)致的钞护,將get方法稍微修改一下先:

修改測試圖

可以發(fā)現(xiàn)的確是get方法錯來盖喷,到那時錯在哪里呢?因為BOOL變量占據(jù)一個內(nèi)存空間难咕,也就是8為课梳,這里我們是將一個只占一個內(nèi)存空間的一位0b1賦值到8位,前面的7個都是直接用0b前面的值補充余佃,也就是1暮刃,這個時候就相當(dāng)于符號為用1來填充,就全部變成來11111111爆土,為什么明明顯示是255椭懊,答案是-1呢,這里牽涉到有符號位和無符號位的關(guān)系雾消,如果有符號位呢灾搏,這里11111111 就是-1 因為這里11111111是補碼挫望,需要返回原碼,返回原碼的操作就是減1取反狂窑,符號位置不變(原碼--》補碼 取反 再加1)媳板,無符號位就是255;

出現(xiàn)這種問題有兩種解決方法泉哈,第一種就是將位域的長度擴大到2蛉幸,也就是占用兩個二進制樹,那么就會變成ob01丛晦,這個時候賦值到8位的BOOL值時候就會用0取填充奕纫,最終就會變成來00000001;第二種方法就是和之前的一樣烫沙,使用Fゲ恪!雙非符號來解決問題锌蓄,最終實現(xiàn)效果如下:

位域Iぁ!

相對來說瘸爽,結(jié)構(gòu)體的位域則不需要使用掩碼您访,代碼可讀性增強,但是效力相比直接使用位運算的方式差剪决,因為最終都是要轉(zhuǎn)化成位運算的操作(匯編環(huán)節(jié))灵汪,所以我們可以采取用共用體,學(xué)習(xí)共用體之后柑潦,我們就可以看源碼里面的一些設(shè)計思路來

共用體

為了提高高效率的同時又能有較強的可讀性享言,可以使用共用體來增強代碼可讀性,同時使用位運算來提高效率

共用體設(shè)計如下:

共用體

get 方法 set方法如下圖:

get set

上面的共用體中_tallRichHandsome1 占用1個字節(jié)妒茬,tall1,rich1,handsome1,thin1 只占一位二進制空間担锤,所以結(jié)構(gòu)體占用一個字節(jié),前面打印結(jié)果有說到乍钻,而char類型的bits也只占一個字節(jié)肛循,所以可以共用一個字節(jié)的內(nèi)存;從上面的方法中可以看到银择,get多糠,set方法并沒有使用到結(jié)構(gòu)體,而結(jié)構(gòu)體的目的是為了增強代碼的可讀性浩考,指明共用體中存儲來哪些值夹孔,以及這些值各占多少位空間,同時存儲取值還使用位運算來增加效率;好啦搭伤,這個時候可以進入查看isa_t 的源碼了只怎。

runtime-isa_t 源碼

isa_t 共用體源碼

有了之前的鋪墊,現(xiàn)在再來看這份源代碼是否有一些似曾相識的感覺呢怜俐?源碼中通過共用體的形式存儲來64位的值身堡,這些值在結(jié)構(gòu)體中被展示出來,通過對bits進行位運算而取出相應(yīng)位置的值拍鲤;

還記得我們之前在OC對象本質(zhì)中提到過一件事贴谎,就是對象的isa指針需要同ISA_MASK經(jīng)過一次& 運算才能得出真正的Class對象地址

isa&ISA_MASK

從源碼中如果是arm64位的話,可以知道這個ISA_MASK的值是define ISA_MASK? 0x0000000ffffffff8ULL季稳,轉(zhuǎn)為二進制表現(xiàn)形式是:0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 1111 1111 1111 1111 1111 1000擅这,可以看到有33位為1:

0x0000000ffffffff8ULL 二進制

所以共用體里面的shifcls中存儲的Class,Meta_Class 對象的內(nèi)存地址信息景鼠,其他的對應(yīng)字段相應(yīng)對應(yīng)源碼字段仲翎,這里有一個特殊點就是,因為這個ISA_MASK最后三位的值都為0铛漓,所以可以得出一個結(jié)論就是任何類對象或者猿類對象的內(nèi)存地址最后三位必定為0谭确,那也就是說明轉(zhuǎn)為16進制之后,類對象或者元類對象的內(nèi)存地址最后一位要不就是8要不就是0票渠;

實例證明

運行環(huán)境:__arm64__位架構(gòu)

實例代碼圖和調(diào)試圖如下:

代碼原圖
終端輸出圖
二進制


isa&ISA_MASK

isa 二進制:? ??????????????0000 0000 0000 0000?

? ??????????????????????????????????0000 0001 1010??0001?

? ??????????????????????????????????0000 0000 0000 1010?

? ??????????????????????????????????1000 1110 0010 0001

isa&ISA_MASK二進制:0000 0000 0000 0000

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?0000 0000 0000 0001

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?0000 0000 0000 1010?

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?1000 1110 0010 0000

上述二進制的加粗部分說明的是取出shiftcls33 位數(shù)據(jù),可以發(fā)現(xiàn)對象isa的33 為和isa&ISA_MASK的加粗33位相同的芬迄,代表這個shiftcls存儲的就是類對象地址或者元類對象地址? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

相信上面講了這么多问顷,大家對isa指針應(yīng)該有了全新的認(rèn)識,在__arm64__架構(gòu)之后禀梳,isa指針不單單只存儲了Class或者Meta-Class的地址杜窄,而是使用了共用體的方式存儲了更多的信息,

其中shiftcls存儲了Class或Meta-Class的地址算途,需要和ISA_MASK進行按位&操作才可以取出其內(nèi)存地址值塞耕,通過上面的演示結(jié)果可以發(fā)現(xiàn)shiftcls和類對象地址存儲的33位二進制完全相同,

extra_rc中的19位存儲引用計數(shù)減1嘴瓤,實例當(dāng)中person的引用計數(shù)位1扫外,因此此時extra_rc的19位二進制存儲的是0

magic中的6位在于判斷對象是否未完成初始化,因為magic是(isa.magic is part of ISA_MAGIC_VALUE)的一部分? ?#define ISA_MAGIC_VALUE 0x000001a000000001ULL(二進制:0000 0000 0000 0000 0000 00001 1010 0000 0000 0000 0000 0000 0000 0000 0000 0001)粗色標(biāo)注的6位就是magic的值:而例子中的person已經(jīng)初始化了,所以magic存儲的就是里面的6位011010

isa 初始化

nonpointer :0 代表普通指針廓脆,存儲這Class筛谚,Meta-Class對象的內(nèi)存地址,1代表表示優(yōu)化后使用位域存儲這更多的信息停忿,這里肯定是使用優(yōu)化后的isa驾讲,因此nonpointer的值肯定是1

這個時候可以看到另外一些字段has_assoc 和 weakly_referenced 值都為0,接著我們繼續(xù)測試這兩個字段,添加弱引用和關(guān)聯(lián)對象吮铭,來觀察一下has_assoc 和 weakly_referenced 變化

代碼圖
二進制圖

二進制:0000 0000 0000 0000

? ? ? ? ? ? ? 0000 0101 1010 0001

? ? ? ? ? ? ? 0000 0000 0000 0000?

? ? ? ? ? ? ? 1000 1110 0110? 1111

可以看到后面3個都是1时迫,說明測試結(jié)果正確

注意:只要設(shè)置過關(guān)聯(lián)對象或者弱引用引用過的對象has_assoc和weakly_referenced 的值就會變成1,不論之后是否將關(guān)聯(lián)對象置為nil或者斷開弱引用

如果沒有設(shè)置過關(guān)聯(lián)對象谓晌,對象釋放時候會更快掠拳,這是因為對象在銷毀時會判斷是否有關(guān)聯(lián)對象進而對對象釋放,上對象釋放的源碼圖:

對象銷毀

可以添加微信一起交流學(xué)習(xí):fslskz

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扎谎,一起剝皮案震驚了整個濱河市碳想,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌毁靶,老刑警劉巖胧奔,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異预吆,居然都是意外死亡龙填,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門拐叉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來岩遗,“玉大人,你說我怎么就攤上這事凤瘦∷藿福” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵蔬芥,是天一觀的道長梆靖。 經(jīng)常有香客問我,道長笔诵,這世上最難降的妖魔是什么返吻? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮乎婿,結(jié)果婚禮上测僵,老公的妹妹穿的比我還像新娘。我一直安慰自己谢翎,他們只是感情好捍靠,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著岳服,像睡著了一般剂公。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吊宋,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天纲辽,我揣著相機與錄音颜武,去河邊找鬼。 笑死拖吼,一個胖子當(dāng)著我的面吹牛鳞上,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吊档,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼篙议,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了怠硼?” 一聲冷哼從身側(cè)響起鬼贱,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎香璃,沒想到半個月后这难,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡葡秒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年姻乓,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眯牧。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡蹋岩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出学少,到底是詐尸還是另有隱情剪个,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布版确,位于F島的核電站禁偎,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏阀坏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一笆檀、第九天 我趴在偏房一處隱蔽的房頂上張望忌堂。 院中可真熱鬧,春花似錦酗洒、人聲如沸士修。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽棋嘲。三九已至,卻和暖如春矩桂,著一層夾襖步出監(jiān)牢的瞬間沸移,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留雹锣,地道東北人网沾。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像蕊爵,于是被迫代替她去往敵國和親辉哥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

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