iOS OC 對象原理探索三

前言:

前面分析了對象創建時alloc是怎麼申請內存空間,通過研究Class的第一個屬性是isa,我們都知道isa的指向其類,對象的isa指向類Class, 那麼對象創申請完內存空間之後是怎麼和isa關聯起來的呢?isa指針是怎麼創建的呢?
isa指針的具體走向是什麼呢?isa指針所佔的8字節64位中具體存儲了那些信息呢?接下來我們來探索一下:

1. isa指針結構

isa源碼

#include "isa.h"
// 聯合體  公用 聯合體裏面最大的那個內存
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_BITFIELD源碼如下:

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   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
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

可以看出在不通架構中isa內成員所佔字節也不相同,這些成員所佔字節之和剛好是64位,8字節。每個成員多佔位數和存儲內存如下:

     nonpointer        : 1字節;
     表示是否對isa指針開啓優化
     0:純isa指針 1:不止是類對象地址,包含了類信息、對象的引用計數等
     
     has_assoc         : 1字節;
     關聯對象標誌,0:沒有  1:存在
     
     has_cxx_dtor      : 1字節;
     該對象是有有C++或objc的析構器,
     有析構函數:做邏輯出來   無: 可以更快的釋放對象

     shiftcls          : 33字節; MACH_VM_MAX_ADDRESS 0x1000000000
     存儲類指針的值   開啓指針優化情況下,在arm64架構中用s33位來存儲類指針
     
     magic             : 6;
     調試器判斷f當前對象是真的對象還是沒有初始化的空間

     weakly_referenced : 1;
     標誌對象是否指向或曾經指向一個ARC的弱引用,無弱引用可更快釋放
     
     deallocating      : 1;
     標誌對象是否正在是否內存
     
     has_sidetable_rc  : 1;
     當前對象引用計數大於10時,則需要借用該變量的存儲進位
     
     extra_rc          : 19
     表示該對象的引用計數值,實際是引用計數減1,
     例: 當前對象引用計數爲10,extra_rc = 9,如果引用計數大於10,
     則需要使用has_sidetable_rc進位進位

2. isa的初始化

ias指針初始化分析如下:

isa是聯合體,具有互斥性,對cls賦值,就不會對bits和isa內的結構體成員進行賦值。
在對新創建的isa_t newisa(0)進行賦值時,根據SUPPORT_INDEXED_ISA判斷,對其進行不通的賦值。

        isa_t newisa(0);

#if SUPPORT_INDEXED_ISA
        assert(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif

        // This write must be performed in a single store in some cases
        // (for example when realizing a class because other threads
        // may simultaneously try to use the class).
        // fixme use atomics here to guarantee single-store and to
        // guarantee memory order w.r.t. the class index table
        // ...but not too atomic because we don't want to hurt instantiation
        isa = newisa;

3. isa關聯對象和類

上面提到isa指針中shiftcls存儲類指針的值,通過打印類.class的值然後>>3,得到的值和通過對isashiftcls成員>>3,再>>20 <<20得到的值相等。

通過研究API object_getClass(object)

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

最終的返回結果是(Class)(isa.bits & ISA_MASK);源碼如下:

inline Class 
objc_object::ISA() 
{
    assert(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

代碼驗證:

LGPerson *object = [LGPerson  alloc];

打印結果:


通過打印對象結構,第一個必然是isa指針,通過對isa & 掩碼 ISA_MASK 得到的值和打印對象的class一致。由此可以看出isa是通過shiftcls和關聯到類的

4. isa的走位

同一個對象可以創建多個?同一個類可以創建多個嗎?類只可以創建一個
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ckJs0ukG-1586242843847)(https://user-gold-cdn.xitu.io/2019/12/24/16f38705b086af5f?w=604&h=265&f=png&s=95565)]

類的內存,第一個位置必然是`isa`,指向`LGPerson`元類
對象是程序猿根據類實例化的
類是代碼編寫的,內存中只有一份,是系統創建的
元類是系統編譯時,系統編譯器創建的,便於方法的編譯

對象的isa指向類對象類對象isa指向元類,元類isa指向什麼呢?

通過打印類結構,分析如下:

對象isa -->類對象 —>元類–>根元類–>根元類

NSObject 根類 -> 根元類

經典isa走位圖

isa:對象 -->類對象 —>元類–>根元類–>根元類
superClass繼承關係NSObject 父類爲nil根元類的父類NSObject

5. 對象的本質

通過對下面代碼的clang -rewrite-objc main.m -o mian.cpp生成的c++文件進行分析

@interface LGPerson : NSObject{
    NSString *name;
}
@property (nonatomic, copy) NSString *name;
@end

@implementation LGPerson

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"123");
    }
    return 0;
}

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CblpEreT-1586242843857)(https://user-gold-cdn.xitu.io/2019/12/25/16f3bd726ade9bb5?w=955&h=560&f=png&s=132211)]
可以看出對象的底層本質是結構體struct,通過property的屬性,底層會自動生成settergetter,並且會生成一個_屬性的成員變量,底層編譯不會生成相應的 settergetter

6. union聯合體補充

1. 什麼是聯合體
1. union中可以定義多個成員,union的大小由最大的成員的大小決定。
2. union成員共享同一塊大小的

內存,一次只能使用其中的一個成員。
3. 對某一個成員賦值,會覆蓋其他成員的值(也不奇怪,因爲他們共享一塊內存。但前提是成員所佔字節數
相同,當成員所佔字節數不同時只會覆蓋相應字節上的值,比如對char成員賦值就不會把整個int成員覆
蓋掉,因爲char只佔一個字節,而int佔四個字節)
4. union的存放順序是所有成員都從低地址開始存放的。

2.聯合體的好處
使代碼存儲數據高效率的同時,有較強的可讀性,可以使用共用體來增強代碼可讀性,同時使用位運算來提高
數據存取的效率
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章