isa_t

isa_t作用

在OC的類結構中出現的第一個成員變量就是聯合體isa_t

struct objc_object {
private:
    isa_t isa;
//在早期的版本中isa只是一個Class類型的指針
//Class _Nonnull isa;

public:
        // ISA() assumes this is NOT a tagged pointer object
    Class ISA();
    ...
}

在早期的32bit版本中isa就是一個單一的指針,用於存儲當前對象的類或者類的元類. 但是在64bit爲操作系統上,用一個8字節指針的長度只存儲一個對象地址顯然是浪費的(操作系統只有一部分地址是可用於存儲對象地址的空間),所以apple對這個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
};

作爲過渡也爲了兼容早期的實現版本,這個結構中保存了變量Class class的實現,同時增加了uintptr_t(unsigned long)類型的變量bits,但由於使用的是聯合體(公用體,共用變量空間),所以該結構只佔用一個指針的空間.當使用bits變量進行存儲時,利用位域結構將變量的各個位進行拆分賦予不同的含義,充分利用了內存空間.

利用位域使得變量內不僅僅保存了指針值,同時還保存了很多有用的信息.

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

# else
#   error unknown architecture for packed isa
# endif

以主流的arm64爲例,主要包含了:

  • nonpointer:佔用1bit,標識是否開啓isa優化.如果是一個指針值該位爲0,則表示當前結構的值只是一個指針沒有保存其他信息;如果爲1,則表示當前結構不是指針,而是一個包含了其他信息的位域結構;
  • has_assoc:當前對象是否使用objc_setAssociatedObject動態綁定了額外的屬性;
  • has_cxx_dtor: 是否含有C++或者OC的析構函數,不包含析構函數時對象釋放速度會更快;
  • shiftcls:   這個值相當於早期實現中的isa指針,是真實的指針值,在arm64處理器上只佔據33位,可見其實在內存中可以用來存儲對象指針的空間是很有限的;
  • magic:用於判斷對象是否已經完成了初始化,在 arm64 中 0x16 是調試器判斷當前對象是真的對象還是沒有初始化的空間(在 x86_64 中該值爲 0x3b);
  • weakly_referenced:是否是弱引用對象;
  • deallocating:對象是否正在執行析構函數(是否在釋放內存);
  • has_sidetable_rc:判斷是否需要用sidetable去處理引用計數;
  • extra_rc:存儲該對象的引用計數值減一後的結果. 當對象的引用計數使用extra_rc足以存儲時has_sidetable_rc=0;當對象的引用計數使用extra_rc不能存儲時has_sidetable_rc=1.可見對象的引用計數主要存儲在兩個地方:如果isa中extra_rc足以存儲則存儲在isa的位域中;如果isa位域不足以存儲,就會使用sidetable去存儲.

isa_t初始化

inline void 
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);


        /*
            此時省略了SUPPORT_INDEXED_ISA預編譯實現,這部分可以忽略,iOS中不使用這一技術
        
        */
        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;


        // 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;
    }
}

在這一初始化過程中:

  • newisa.bits = ISA_MAGIC_VALUE;首先使用ISA_MAGIC_VALUE將bits進行初始化,主要有兩個作用:
    • 將nonpointer標記爲1,證明開啓了isa優化;
    • 初始化magic位,用於標記該對象已經初始化.
  • newisa.has_cxx_dtor = hasCxxDtor;初始化對象標記是否有C++或者OC析構函數,如果存在在對象釋放時需要消耗更多時間來對對象進行析構;
  • newisa.shiftcls = (uintptr_t)cls >> 3;將對象指向的類或者類的元類指針賦值給位域shiftcls,因爲前三位被nonpointer,has_assoc和has_cxx_dtor被佔據,所以需要將真實的指針左移三位進行復制保存.

ISA_MASK

在iOS 64bit的操作系統中,爲了屏蔽外界對於isa指針的直接獲取對外部隱藏了真實的isa指針的值,而是需要使用ISA_MASK進行與操作來獲取到真實的isa.

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
}

所以如果想要通過獲取到對象真實的isa指針指向需要與ISA_MASK進行與操作:

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# else
#   error unknown architecture for packed isa
# endif


NSObject *obj = [[NSObject alloc] init];
void *ptr = (__bridge void *)obj;//牆紙轉化爲指針
void *isa = *(void **)ptr; //獲取類中第一個成員變量isa_t值
isa = (void *)((uintptr_t)isa & ISA_MASK); //獲取到isa真實的指針地址
id isaObj = (__bridge id)isa;//強轉爲id,即可獲取到isa指向的真實對象
NSLog(@"isaObj == %@", isaObj);

該過程也可以通過LLDB實現:

(lldb) po (void *)obj
<NSObject: 0x10110f400>

(lldb) x/2g 0x10110f400
0x10110f400: 0x001d800100b38141 0x0000000000000000
(lldb) p/x (0x001d800100b38141 & ISA_MASK) //根據需要替換ISA_MASK
(unsigned long long) $1 = 0x0000000100b38140
(lldb) po $1
NSObject

ISA_MAGIC_MASK

從isa_t位域的定義中可以知道,magic變量佔據了6bit的空間,使用ISA_MAGIC_MASK就可以快速地從isa_t中獲取到magic值.

__arm64__中,位域magic佔據了從37位(1(nonpointer)+1(has_assoc)+1(has_cxx_dtor)+33(shiftcls) )開始的6bit空間,而ISA_MAGIC_MASK=0x000003f000000001ULL,除第37-42位和最低位外所有位均爲0;在__x86_64__中,位域magic佔據了從47位(1(nonpointer)+1(has_assoc)+1(has_cxx_dtor)+44(shiftcls))開始的6bit空間,而ISA_MAGIC_MASK=0x001f800000000001ULL,除第44-49位和最低位外其餘位均爲0.所以使用isa.bits & ISA_MAGIC_MASK,可以快速判斷出是否開啓nonpointer優化和magic的值.

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define MAGIC_MASK      36
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define MAGIC_MASK      47
# else
#   error unknown architecture for packed isa
# endif

NSObject *obj = [[NSObject alloc] init];
void *ptr = (__bridge void *)obj;
ptr = *(void **)ptr;
uintptr_t magic = (uintptr_t)(void *)((uintptr_t)ptr & ISA_MAGIC_MASK);
if (magic & 1) {
    NSLog(@"magic == %lu", magic >> MAGIC_MASK);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章