iOS底層原理 03 : OC對象&isa

前言:
OC對象的本質到底是什么?里面到底是什么結構呢?我們常說的isa是什么?我們在iOS底層原理 01 : alloc&init中探索的obj->initInstanceIsa(cls, hasCxxDtor)是將isa與類信息關聯的,那么isa具體是如何與類信息關聯的呢?下面我們帶著這些問題一起來學習。

OC對象的本質

接下來我們一起探索OC對象的本質:

  • 首先我們在新建好的工程的main.c文件里面自定義一個LGHPerson類
@interface LGHPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation LGHPerson
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        LGHPerson *person = [LGHPerson alloc];
        NSLog(@"Hello, World!");
    }
    return 0;
}
  • 然后打開終端,將mian.c文件通過clang命令編譯成我們需要的main.cpp, 即:在終端輸入:clang -rewrite-objc main.c -o main.cpp

  • 打開mian.cpp,快速查詢LGHPerson,我們會發現如下code,我們發現clang編譯器會把LGHPerson編譯成struct LGHPerson_IMPL{}結構體,所以LGHPerson類的本質其實就是LGHPerson_IMPL{}的結構體

struct LGHPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS; //isa
    NSString *_name;
};

總結:
OC對象的本質是結構體,每一個結構體都有struct NSObject_IMPL NSObject_IVARS的成員變量(即isa),OC類的屬性會被編譯成對應結構體的成員變量。

探索obj->initInstanceIsa()具體實現?

我們從objc4源碼(傳送:可編譯調試的objc4源碼),找到initInstanceIsa()這個函數

  • initInstanceIsa()里面會執行initIsa()
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}
  • initIsa()函數里面,如果是!nonpointer, 通過初始化函數isa_t((uintptr_t)cls)并賦值給isa,否則新建isa_t,并對isa_t的位域里面的變量進行賦初始值
inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {
        isa = isa_t((uintptr_t)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;
    }
}
initInstanceIsa流程圖.png

isa的本質

接下來我們來探索一下isa,我們在initIsa()函數里面,我們看到isa是一個isa_t的聯合體

1. 最終我們在isa.h文件中,看到isa_t的結構

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  
    };
#endif
};

2. ISA_BITFIELD宏定義,我們看到定義的是位域, 在__arm64____x86_64__下里面定義的變量是相同的,只是所占的字節數略微有些不同。

# if __arm64__
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
# elif __x86_64__
#   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
endif

3. 所以其實isa_t的真正結構是聯合體位域__x86_64__下,我們可以看到位域里面所有變量所占的字節總數是64位,恰好是8字節

union isa_t {
    Class cls;
    uintptr_t bits;
    struct {
       uintptr_t nonpointer       : 1;                                  
      uintptr_t has_assoc         : 1;                                         
      uintptr_t has_cxx_dtor      : 1;                                        
      uintptr_t shiftcls          : 44; 
      uintptr_t magic             : 6;                                       
      uintptr_t weakly_referenced : 1;                                       
      uintptr_t deallocating      : 1;                                        
      uintptr_t has_sidetable_rc  : 1;                                        
      uintptr_t extra_rc          : 8;  
    };
};

4. 那么每個的作用是什么呢?這里總結一下:

  • nonpointer:表示是否對 isa 指針開啟指針優化, 0:純isa指針,1:不?是類對象地址,isa 中包含了類信息、對象的引?計數等 (自定義的類nonpointer為1)

  • has_assoc:關聯對象標志位,0沒有,1存在

  • has_cxx_dtor:該對象是否有 C++ 或者 Objc 的析構器,如果有析構函數,則需要做析構邏輯, 如果沒有,則可以更快的釋放對象

  • shiftcls: 存儲類指針的值。開啟指針優化的情況下,在 arm64 架構中有 33 位?來存儲類指針,x86架構用44位來存儲

  • magic:?于調試器判斷當前對象是真的對象還是沒有初始化的空間

  • weakly_referenced:志對象是否被指向或者曾經指向?個 ARC 的弱變量,沒有弱引?的對象可以更快釋放。

  • deallocating:標志對象是否正在釋放內存

  • has_sidetable_rc:當對象引?技術?于 10 時,則需要借?該變量存儲進位

  • extra_rc:當表示該對象的引?計數值,實際上是引?計數值減 1,例如,如果對象的引?計數為 10,那么 extra_rc 為 9。如果引?計數?于 10,則需要使?到下?的 has_sidetable_rc。

總結:
1.isa_t是一個聯合體,里面的成員變量共用一段內存,成員變量之間setter方法互斥(不能同時set),所以對cls賦值的時候就不能對bits賦值,對bits賦值的時候就不能對cls賦值。
2.shiftcls是用來存儲類信息的。

使用lldb去體驗isa_t的結構

接下來我們去驗證到底LGHPerson類信息是否存儲到shiftcls里面?

  • 首先斷點到initIsa()函數里面


    斷點.png
  • 我們使用lldb打印一些信息
    newisa.bits = ISA_MAGIC_VALUE; 是給nonpointermagic附上初始值。
(lldb) p newisa
(isa_t) $12 = {
  cls = 0x001d800000000001
  bits = 8303511812964353
   = {
    nonpointer = 1
    has_assoc = 0
    has_cxx_dtor = 0
    shiftcls = 0
    magic = 59
    weakly_referenced = 0
    deallocating = 0
    has_sidetable_rc = 0
    extra_rc = 0
  }
}

從下面打印的信息來看,newisa.clsnewisa.bits共用同一段內存,0x001d800000000001的10進制就是8303511812964353。

(lldb) p  newisa.cls
(Class) $9 = 0x001d800000000001
(lldb) p  newisa.bits
(uintptr_t) $10 = 8303511812964353
(lldb) p/d  0x001d800000000001
(long) $11 = 8303511812964353

我們接著斷點往下走

圖片.png

打印 newisa,我們看到shiftcls已經有值了,因為newisa.shiftcls = (uintptr_t)cls >> 3;將類存到shiftcls

p newisa
(isa_t) $13 = {
  cls = LGPerson
  bits = 8303516107940081
   = {
    nonpointer = 1
    has_assoc = 0
    has_cxx_dtor = 0
    shiftcls = 536871966
    magic = 59
    weakly_referenced = 0
    deallocating = 0
    has_sidetable_rc = 0
    extra_rc = 0
  }
}

我們看到(uintptr_t)cls >> 3 是536871966,newisa.shiftcls的值536871966,說明類信息確實是存放在shiftcls。

(lldb) p (uintptr_t)cls >> 3
(uintptr_t) $17 = 536871966
(lldb) p newisa.shiftcls
(uintptr_t) $18 = 536871966

驗證shiftcls確實是存放的是類信息(在__x86__架構為例)

  • 先在main函數打個斷點


    圖片.png
  • 使用lldb打印一些信息
    通過lldb查看objc的內存,得到isa0x001d8001000020e9
(lldb) po objc
<LGHPerson: 0x100681f90>
(lldb) x/4gx  0x100681f90
0x100681f90: 0x001d8001000020e9 0x0000000000000000
0x100681fa0: 0x756e654d534e5b2d 0x776569566d657449

我們通過移位操作,獲取isa[3-46]位置上的值,通過與LGHPerson.class比對,發現是一致的,都是0x00000001000020e8

(lldb) p/x 0x001d8001000020e9>>3
(long) $4 = 0x0003b0002000041d
(lldb) p/x  0x0003b0002000041d<<20
(long) $5 = 0x0002000041d00000
(lldb) p/x  0x0002000041d00000>>17
(long) $6 = 0x00000001000020e8
(lldb) p/x LGHPerson.class
(Class) $7 = 0x00000001000020e8 LGHPerson

我們也可以通過算法 & ISA_MASK (0x00007ffffffffff8ULL),來得到shiftcls的值,
我們看的也能得到0x00000001000020e8

(lldb) p/x 0x001d8001000020e9 & 0x00007ffffffffff8ULL
(unsigned long long) $8 = 0x00000001000020e8

補充

lldb相關的調試命令

p/x  //以16進制輸出
p/d  //以10進制輸出
p/o // 以8進制輸出
p/t // 以2進制輸出
p/c  // 打印字符
x   // 讀取內存 (等價于 memory read)
memory read   // 讀取內存
x/4gx    // 讀取4段內存(每段8字節,即32字節)
x/6gx    // 讀取6段內存(每段8字節,即48字節)
x/8gx    // 讀取8段內存(每段8字節,即64字節)
po <expr>     // 打印對象的 description 方法的結果
help      // 列出所有的命令
help <command>       // 列出某個命令更多的細節,例如 help print

移位運算
比如有一段8位的內存,我們如何從取出【2-6】位上的值呢?請看下面示意圖:

>>2 ,然后<<3 ,最后 >>1,最后得到0111 1000

移位運算圖.png

LLVM與Clang

  1. LLVM構架編譯器(compiler)的框架系統,以C++編寫而成,用于優化以任意程序語言編寫的程序的編譯時間(compile-time)、鏈接時間(link-time)、運行時間(run-time)以及空閑時間(idle-time),對開發者保持開放,并兼容已有腳本。
  2. Clang是一個C++編寫、基于LLVM、發布于LLVM BSD許可證下的C/C++/Objective-C/Objective-C++編譯器。比起GCC,Clang編譯速度快占用內存小非常方便進行二次開發

下圖是LLVM和Clang的關系圖


LLVM和Clang的關系圖.png

總結 :
1. Clang相當于編譯過程的前端,而LLVM相當于編譯過程的后端。
2. Clang側重于語法語義分析生成中間代碼,而LLVM側重于代碼優化生成目標程序
3.clang命令編譯mian.c的指令:clang -rewrite-objc main.m -o main.cpp

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