OC 對象原理探索(三):對象的本質 & isa

1. 聯合體、位域

1.1 結構體

先看下面的代碼:markdown

struct SSLCar {
    BOOL front;
    BOOL back;
    BOOL left;
    BOOL right;
}sslCar;

NSLog(@"sslCar:%lu",sizeof(sslCar));

打印結果:sslCar:4
複製代碼

咱們看到一個SSLCar結構體是4個字節,也就是32位(0000 0000 0000 0000 0000 0000 0000 0000),而SSLCar結構體中4BOOL值只須要4位(1111)就能夠存儲,這形成了很是大的空間浪費。架構

1.2 位域

經過位域指定每一個成員變量佔1位:ide

struct SSLCar2 {
    BOOL front: 1;
    BOOL back : 1;
    BOOL left : 1;
    BOOL right: 1;
}sslCar2;

NSLog(@"sslCar2:%lu",sizeof(sslCar2));

打印結果:sslCar2:1
複製代碼

此時內存空間已經獲得了不錯的優化。函數

1.3 聯合體

建立聯合體:post

union SSLPerson {
    char *name;
    int  age;
};
複製代碼

爲聯合體賦值,並打印:優化

經過打印結果,發現聯合體只有後賦值的變量纔有值。ui

1.4 結構體和聯合體的區別

結構體(struct)中全部變量是「共存」的——優勢是「有容乃大」,全面;缺點struct內存空間的分配是粗放的,無論用不用,全分配。this

聯合體(union)中是各變量是「互斥」的——缺點就是不夠「包容」; 但優勢是內存使用更爲精細靈活,也節省了內存空間。atom

2. 對象本質

main.m中添加以下代碼:spa

@interface SSLPerson : NSObject

@property (nonatomic, copy) NSString *sslName;

@end

@implementation SSLPerson

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
    }
    return 0;
}
複製代碼

經過clang命令編譯main.m(更多clang信息點這裏):

clang -rewrite-objc main.m -o main.cpp
複製代碼

獲得main.cpp文件,在main.cpp中咱們能夠看到有關對象的信息:

typedef struct objc_class *Class; // 7658行

struct objc_object { // 7661 行
    Class _Nonnull isa __attribute__((deprecated));
};

typedef struct objc_object *id; // 7666行

typedef struct objc_object SSLPerson; // 111788行

struct SSLPerson_IMPL { // 111793行
	struct NSObject_IMPL NSObject_IVARS;
	NSString *_sslName;
};
複製代碼

由此咱們能夠得出,對象是一個objc_object類型的結構體,內部有一個Class類型的isa指針。

打開源碼,查看objc_object

struct objc_object {
private:
    isa_t isa;
    ...
}
複製代碼

3. isa 結構分析

咱們在 OC 對象原理探索(一) 中,提到過obj->initIsa(cls),它的做用是將指針和類進行關聯,下面咱們用源碼進行詳細的分析。

initIsa函數:

inline void 
objc_object::initIsa(Class cls)
{
    initIsa(cls, false, false);
}

objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) {
    ASSERT(!isTaggedPointer()); 
    
    isa_t newisa(0); // isa初始化

    if (!nonpointer) {
        newisa.setClass(cls, this);//若是是純指針 isa被直接cls賦值
    } else { 
       ASSERT(!DisableNonpointerIsa);
       ASSERT(!cls->instancesRequireRawIsa());
#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;
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }
    isa = newisa;
}
複製代碼

點擊查看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;  // defined in isa.h
    };
#endif
};
複製代碼

點擊查看ISA_BITFIELD

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   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
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

# 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的內存分佈圖:

  • nonpointer:表示是否對isa指針開啓指針優化。0:純isa指針,1:不止是類對象地址,isa中包含了類信息、對象的引用計數等。
  • has_assoc:關聯對象標誌位,0沒有,1存在。
  • has_cxx_dtor:該對象是否有C++或者Objc的析構器,若是有析構函數,則須要作析構邏輯, 若是沒有,則能夠更快的釋放對象。
  • shiftcls:存儲類指針的值。開啓指針優化的狀況下,在arm64架構中有33位用來存儲類指針。
  • magic:用於調試器判斷當前對象是真的對象仍是沒有初始化的空間。
  • weakly_referenced:標誌對象是否被指向或者曾經指向一個ARC的弱變量,沒有弱引用的對象能夠更快釋放。
  • deallocating:標誌對象是否正在釋放內存。
  • extra_rc:表示該對象的引用計數值,若是不夠存儲,has_sidetable_rc的值就會變爲 1;
  • has_sidetable_rc:若是爲1,表明引用計數過大沒法存儲在isa中,那麼超出的引用計數會存儲在SideTableRefCountMap中。因此,若是isanonpointer,則對象的引用計數存儲在它的isa_textra_rc中以及SideTableRefCountMap中。

總結:

  • isa分爲nonpointer類型和非nonpointer類型。非nonpointer類型是一個純指針,nonpointer類型還包含了引用計數、關聯對象等信息。
  • isa採用聯合體+位域的方式存儲信息。由於在iOS中萬物皆對象,對象中都用到了isa,這樣能夠大大的節省內存空間。

4. isa 關聯類

先建立一個SSLperson類,初始化[SSLPerson alloc],在initIsa函數中斷點調試:

image.png

如上圖,建立了一個聯合體newisa,裏邊的變量目前都是0,沒有賦值。

繼續向下運行:

image.png

如上圖,newisa.bits = ISA_MAGIC_VALUEISA_MAGIC_VALUE是一個宏 =0x001d800000000001。被賦值的變量有bits=8303511812964353cls=0x001d800000000001nonpointer=1magic=59。轉化成二進制表示:

image.png

  • 0x001d800000000001=8303511812964353,它們的二進制表示相同。
  • nonpointer的二進制表示是1magic的二進制表示是111011,它們包含在bits的二進制表示中。

斷點進入setClass

image.png

  • 這裏,isaSSLPerson類進行了關聯。
  • 目前是nonpointer,因此並不會給cls賦值。
  • SSLPerson的類地址>>3進行10進制轉換,而後賦值給shiftcls
    • 爲何要右移3位呢,由於shiftclsisa中是從第四位開始存儲的,這樣isa & ISA_MASK才能獲得準確的類地址

繼續向下運行:

image.png image.png

  • shiftclsextra_rc也被成功賦值。
  • 這裏我一直有個疑惑cls明明沒有被賦值,爲何倒是SSLPerson呢,並且聯合體也應該會把cls覆蓋掉纔對,因此我後期有作了一個調試:
    • image.png
    • 打印了cls的值,它確實是整個isa的值,至於爲何顯示SSLPerson,應該是系統作了處理。

到此,isa關聯的探索基本完成,之後若有更深刻理解會持續更新。

相關文章
相關標籤/搜索