Runtime之isa詳解

isa 概念

isa是相當于是OC對象的一個標識指針,只要是OC對象就一定會有isa指針,arm64之前isa就是一個指向對象或者類的指針而已,在arm64之后發生了一些改進,isa在arm64之后變成了一個共用體(union)結構,同時使用位域的思想來實現,達到節省內存的作用;從源碼中,我們可以看到一個OC對象的isa指針并不是直接指向類對象或者元類對象的,而是要通過一個&ISA_MASK才能獲取到真正的類對象或者元類對象,這個是為什么呢?在分析之前,我們有必要先弄懂一下共用體的相關概念和用法;

共用體概念

在進行某些算法的C語言編程的時候,需要使幾種不同類型的變量存放到同一段內存單元中。也就是使用覆蓋技術,幾個變量互相覆蓋。這種幾個不同的變量共同占用一段內存的結構,在C語言中,被稱作“共用體”類型結構,簡稱共用體。

尋找真理

現在假設一個Person對象有3個布爾類型變量tall,rich,handsome,那么如果我們平時定義的時候,肯定都是通過屬性變量來定義,例如下圖:

代碼圖

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

char 聲明圖

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

初始化

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

tall,rich,handsome對應圖

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

二進制的賦值操作

前面說過BOOL變量只有0和1,那么賦值操作就是只要將對應的位置設置為0或者1就可以了,賦值為1呢,我們使用 | (按位或)操作,因為按位或的做法是只要有一個為1,結果為1,所以這里我們如果想將某一位賦值為1的話,就將原來的值和相應的掩碼進行按位或的操作就可以了,例如現在我想將tall賦值為1

根據前面的tall,rich,handsome的圖,我們這里需要將第三位賦值為1,如下圖所示

tall賦值操作

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

handsome賦值為0

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

set方法

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

二進制的取值操作

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

取值操作

從圖中的展示結果可以看到這樣的取值操作是沒有問題的,所以我們的get方法實現應該如下:

get方法實現

#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)

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

測試源碼
沒有加上!!

從結果中可以看到,這個結果不是我們想要的,我們不能這樣返回一個BOOL變量值的,因為這個值返回去有可能是一個大于1的整數,所以這里我們采取來兩個!!符號來實現,如果&的結果之后是0,那!0 代表1,!1代表0,正確,如果&結果返回1,!1是0,!0是1,返回結果也爭取,所以上面的get方法更改成如下:

get方法糾正

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

位域

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

位域形式大概如下:

位域

set get方法實現如下:

set get方法

測試代碼和結果如下:

測試結果圖

發現結果居然驚現-1,但是log打斷點顯示賦值已經是正確的了

終端調試圖

上面計算機那里的07 可以看到后面3為都是111,說明已經是賦值成功的了,為什么直接拿07,不拿多其他多個字節呢,是因為_tallRichHandsome只占據一個內存空間,也就是一個字節,可能會有人疑問為什么是1個字節而不是三個字節,這個應該就是位域的性質了,看下圖吧:

結構體
位域

上面的結果都能顯示我們的賦值操作是正確的,那么出錯的可能就應該是取值的時候操作有無導致的,將get方法稍微修改一下先:

修改測試圖

可以發現的確是get方法錯來,到那時錯在哪里呢?因為BOOL變量占據一個內存空間,也就是8為,這里我們是將一個只占一個內存空間的一位0b1賦值到8位,前面的7個都是直接用0b前面的值補充,也就是1,這個時候就相當于符號為用1來填充,就全部變成來11111111,為什么明明顯示是255,答案是-1呢,這里牽涉到有符號位和無符號位的關系,如果有符號位呢,這里11111111 就是-1 因為這里11111111是補碼,需要返回原碼,返回原碼的操作就是減1取反,符號位置不變(原碼--》補碼 取反 再加1),無符號位就是255;

出現這種問題有兩種解決方法,第一種就是將位域的長度擴大到2,也就是占用兩個二進制樹,那么就會變成ob01,這個時候賦值到8位的BOOL值時候就會用0取填充,最終就會變成來00000001;第二種方法就是和之前的一樣,使用!!雙非符號來解決問題,最終實現效果如下:

位域!!

相對來說,結構體的位域則不需要使用掩碼,代碼可讀性增強,但是效力相比直接使用位運算的方式差,因為最終都是要轉化成位運算的操作(匯編環節),所以我們可以采取用共用體,學習共用體之后,我們就可以看源碼里面的一些設計思路來

共用體

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

共用體設計如下:

共用體

get 方法 set方法如下圖:

get set

上面的共用體中_tallRichHandsome1 占用1個字節,tall1,rich1,handsome1,thin1 只占一位二進制空間,所以結構體占用一個字節,前面打印結果有說到,而char類型的bits也只占一個字節,所以可以共用一個字節的內存;從上面的方法中可以看到,get,set方法并沒有使用到結構體,而結構體的目的是為了增強代碼的可讀性,指明共用體中存儲來哪些值,以及這些值各占多少位空間,同時存儲取值還使用位運算來增加效率;好啦,這個時候可以進入查看isa_t 的源碼了。

runtime-isa_t 源碼

isa_t 共用體源碼

有了之前的鋪墊,現在再來看這份源代碼是否有一些似曾相識的感覺呢?源碼中通過共用體的形式存儲來64位的值,這些值在結構體中被展示出來,通過對bits進行位運算而取出相應位置的值;

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

isa&ISA_MASK

從源碼中如果是arm64位的話,可以知道這個ISA_MASK的值是define ISA_MASK? 0x0000000ffffffff8ULL,轉為二進制表現形式是:0000 0000 0000 0000 0000 0000 1111 1111 1111 1111 1111 1111 1111 1111 1111 1000,可以看到有33位為1:

0x0000000ffffffff8ULL 二進制

所以共用體里面的shifcls中存儲的Class,Meta_Class 對象的內存地址信息,其他的對應字段相應對應源碼字段,這里有一個特殊點就是,因為這個ISA_MASK最后三位的值都為0,所以可以得出一個結論就是任何類對象或者猿類對象的內存地址最后三位必定為0,那也就是說明轉為16進制之后,類對象或者元類對象的內存地址最后一位要不就是8要不就是0;

實例證明

運行環境:__arm64__位架構

實例代碼圖和調試圖如下:

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


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 位數據,可以發現對象isa的33 為和isa&ISA_MASK的加粗33位相同的,代表這個shiftcls存儲的就是類對象地址或者元類對象地址? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

相信上面講了這么多,大家對isa指針應該有了全新的認識,在__arm64__架構之后,isa指針不單單只存儲了Class或者Meta-Class的地址,而是使用了共用體的方式存儲了更多的信息,

其中shiftcls存儲了Class或Meta-Class的地址,需要和ISA_MASK進行按位&操作才可以取出其內存地址值,通過上面的演示結果可以發現shiftcls和類對象地址存儲的33位二進制完全相同,

extra_rc中的19位存儲引用計數減1,實例當中person的引用計數位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)粗色標注的6位就是magic的值:而例子中的person已經初始化了,所以magic存儲的就是里面的6位011010

isa 初始化

nonpointer :0 代表普通指針,存儲這Class,Meta-Class對象的內存地址,1代表表示優化后使用位域存儲這更多的信息,這里肯定是使用優化后的isa,因此nonpointer的值肯定是1

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

代碼圖
二進制圖

二進制:0000 0000 0000 0000

? ? ? ? ? ? ? 0000 0101 1010 0001

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

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

可以看到后面3個都是1,說明測試結果正確

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

如果沒有設置過關聯對象,對象釋放時候會更快,這是因為對象在銷毀時會判斷是否有關聯對象進而對對象釋放,上對象釋放的源碼圖:

對象銷毀

可以添加微信一起交流學習:fslskz

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,003評論 2 374

推薦閱讀更多精彩內容