iOS底層-一應俱全的isa

1.本文概述

  • 本文主要從底層分析isa的做用,isa的數據結構,isa內各個位置內實際存儲的內容,以及isa在是不是nonpointer下的區別
  • isa的走位圖,SuperClass的指向

備註:isa是串聯對象和類的重要線索,瞭解isa,能對對象的本質,類方法的走向等有更深入的理解複製代碼


2.isa的做用

        上篇說到,alloc在開闢空間後也初始化了isa,從而把對象和類關聯起來。因此對於對象來講,isa的基礎做用就是和類進行綁定,告訴系統對象的歸屬。可是大部分nonpointerisa不只僅只是作指向,其內部還存儲了大量的信息。bash

這裏引入了一個nonpointer的概念,簡單說明下:數據結構

早期調用 isa能夠直接返回類,後來蘋果爲了優化內存,使其內部增長了及其豐富的信息,而且增長了 isa_mask,不讓直接獲取類。有優化的就是提到的 nonpointer,也是本文研究的重點。

從初始化isa的源碼,來驗證下這個說法
多線程

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;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        isa = newisa;
    }
}複製代碼

首先斷言判斷架構

assert(!isTaggedPointer());複製代碼

若是是TaggedPointer,後面就不執行,也就沒有isa這個概念了。
app

這裏引入了一個TaggedPointer的概念:ide

        早期64位系統時,當咱們存儲基礎數據類型 , 底層封裝成 NSNumber 對象 , 也會佔用 8字節 內存 , 32位機器佔用4字節。 爲了存儲和訪問一個 NSNumber 對象,須要在堆上分配內存,另外還要維護它的引用計數,管理它的生命期 。這些都給程序增長了額外的邏輯,形成運行效率上的損失 。 所以若是沒有額外處理 , 會形成很大空間浪費 .

所以蘋果引入了TaggedPointer,當對象爲指針爲TaggedPointer類型時,指針的值不是地址了,而是真正的值,直接優化了存儲,提高了獲取速度。函數

TaggedPointer的特色
優化

  • 專門用來存儲小對象,例如NSNumber和部分NSString
  • 指針不在存儲地址,而是直接存儲對象的值。因此,它不是一個對象,而是一個假裝成對象的普通變量。內存也不在堆,而是在棧,由系統管理,不須要mallocfree
  • 在內存讀取上有着3倍的效率,建立時比之前快106倍。(少了malloc流程,獲取時直接從地址提取值)

回到源碼來,ui

if (!nonpointer) {isa.cls = cls;}spa

這裏驗證了,未開啓isa指針優化時,isa直接和類關聯,無後續操做;當開啓優化時,先初始化了isa_t

isa_t newisa(0);複製代碼

,而後對內部屬性賦值,最後經過shiftcls類關聯

newisa.shiftcls = (uintptr_t)cls >> 3;複製代碼

這能夠說明isa_t就是isa真正的結構。


3.isa 的數據結構

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
};複製代碼

isa的底層是isa_tisa_t的結構是聯合體+位域

  • 聯合體的大小取決於內部最大的元素的大小,因此isa的大小爲8字節。
  • 聯合體內部的的元素在內存中的互相覆蓋的,因此cls,bits是不會同時存在的。

回過頭看,nonpointer!nonpointer是隻能二選一的,蘋果就是利用這種互斥關係,把isa的結構定義成聯合體。追求內存極致優化的蘋果顯然不知足於此,在聯合體內又增長了位域的結構來使isa一應俱全。

  • 8字節即爲64個二進制位。每一個二進制位都定義了存儲的內容,即爲位域。

看下ISA_BITFIELD宏定義的每一個二進制位存儲的內容(這裏採用x86架構下的結構,每種架構都有細微的差異,但所包含的內容是同樣的,只是某些內容存儲的長度不一致)

# 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複製代碼

說明下各個存儲位表明的意思:

nonpointer:表示是否對 isa 指針開啓指針優化( 0:純isa指針,1:不⽌是類對象地址,isa 中包含了類信息、對象的引⽤計數等)
has_assoc:關聯對象標誌位(0沒有,1存在)
has_cxx_dtor:該對象是否有 C++ 或者 Objc 的析構器,若是有析構函數,則須要作析構邏輯,若是沒有,則能夠更快的釋放對象
shiftcls:存儲類指針的值。開啓指針優化的狀況下,在arm64架構下有33位用來存儲類指針
magic :用於調試器判斷當前對象是真的對象仍是沒有初始化的空間 
weakly_referenced :對象是否被指向或者曾經指向⼀個 ARC 的弱變量, 沒有弱引⽤的對象能夠更快釋放。
deallocating:標誌對象是否正在釋放內存 
has_sidetable_rc:當對象引⽤技術⼤於 10 時,則須要借⽤該變量存儲進位
extra_rc:當表示該對象的引⽤計數值,其實是引⽤計數值減 1, 例如,若是對象的引⽤計數爲 10,那麼 extra_rc 爲 9。若是引⽤計數⼤於 10, 則須要使⽤到下⾯的 has_sidetable_rc。

實際演練驗證一下,初始化兩個對象

CJPerson *object = [CJPerson alloc];
NSLog(@"object = %p", object);        

CJPerson *object1 = [CJPerson alloc];                
objc_setAssociatedObject(object1, @"object1", object1, OBJC_ASSOCIATION_RETAIN_NONATOMIC);        
NSLog(@"object1 = %p", object1);複製代碼

lldb打印出它們各自的isa內容


能夠看出,第一位是同樣的,由於都是nonpointer , 惟一的區別就是在第二位關聯對象標誌位。其餘位置同理,有個比較特殊的就是shiftcls,上文提到,早期isa能夠直接獲取類,如今須要一個isa_mask來間接獲取。

看下object_getClass底層,有一段這樣的代碼,也是經過isa_mask來獲取isa的指向

return (Class)(isa.bits & ISA_MASK);複製代碼

x86中的isa_mask

# define ISA_MASK 0x00007ffffffffff8ULL複製代碼

轉爲二進制

0000 0000 0000 0000 0111 1111 1111 1111
1111 1111 1111 1111 1111 1111 1111 1000複製代碼

這就很明顯了,從第3位開始,後面的44位存儲着shiftcls的信息,恰好和上面給出的x86isa的存儲內容架構吻合。直接將對象的  isa & isa_mask以後,就會獲得對象的內存地址,也就是isa的指向。


3.isa的走位圖

這是蘋果官方給出的isasuperclass的走位圖。


舉個例子驗證下,用上面CJPerson實例出來的對象lldb打印下isa的走位


x/4gx打印的是對象在內存中從首地址開始,連續存儲的4個8字節的內容地址, x/5gxx/6gx依此類推。
p/t p/o p/d p/x 分別表明二進制、八進制、十進制和十六進制打印 。


由於isa是對象中的第一個元素,因此x/4gx打印出來的第一個地址就是isa,在用isa&isa_mask就獲得isa的指向,依次類推



在分別po打印下地址



得出結論:

  • 對象的isa指向類。
  • 類的isa指向和類同名的元類,可是地址不一樣。
  • 元類的isa指向根元類。
  • 根元類的isa指向本身。


總結:
isa是串聯對象,類,元類和根元類的重要線索,採用 聯合體加位域的數據結構使有限的空間充分利用,存儲了豐富的信息


以上就是關於isa的探索,後續繼續更新類的底層結構,方法轉發,block,鎖,多線程等底層探索,還有應用程序加載,啓動優化,內存優化等相關知識點,敬請關注。

相關文章
相關標籤/搜索