OC對象(三)-- isa結構分析

isa


實例對象在內存中首地址就是isa,其實就是用來表示對象的類是誰。c++

DZPerson *obj = [[DZPerson alloc] init];
obj.name = @"DZ";
NSLog(@"%@", obj.name);
複製代碼

經過lldb打印obj的內存狀況: 經過調試,po一下obj首地址中的第一個值是DZPerson。說明isa中存儲着實例對象對應的歸宿類。api

isa結構


經過objc源碼查看isa是一個聯合體位域的形式,源碼以下:markdown

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

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD) //ISA_BITFIELD存在
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
複製代碼

條件編譯命令中的ISA_BITFIELD是存在的,因此後面的代碼是會被編譯進去的。接下來看看ISA_BITFIELD是如何定義的ide

# 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)
複製代碼
  • 這裏使用條件編譯命令區分__arm64__表明iOS。__x86_64__表明MAC
  • ISA_MASK:掩碼,用於直接獲取isa中類信息的。也就是經過掩碼能夠直接獲得shiftcls
  • nonpointer:表示是否對isa指針開啓指針優化
    • 0:isa就是一個指針
    • 1:isa不僅是一個簡單的指針,裏面還包含了類信息,引用計數等等信息。一般自定義的類實例對象都是這個類型。
  • has_assoc:是否有關聯對象
  • has_cxx_dtor:是否有c++或Objc析構器
  • shiftcls:存儲類指針的值。注意iOS和Mac中佔用的位數不同
  • magic:⽤於調試器判斷當前對象是真的對象仍是沒有初始化的空間
  • weakly_referenced:對象有沒有被若引用
  • deallocating:對象是否正在釋放中
  • has_sidetable_rc:是否有引用計數散列表
  • extra_rc:提供給引用計數使用的。

擴展 - 聯合體位域

聯合體位域能夠節省內存空間的開闢,舉個例子: 定義一個DZPeront類,裏面含有四個屬性,分別表明走路的四個方向優化

@interface DZPerson : NSObject
@property (assign, nonatomic) BOOL front;
@property (assign, nonatomic) BOOL back;
@property (assign, nonatomic) BOOL left;
@property (assign, nonatomic) BOOL right;
@end
複製代碼

這四個屬性都是BOOL類型,一個BOOL類型佔用1個字節。所以須要內存開闢4字節的空間。BOOL類型其實就是兩種值YES or NO,對應的二進制是0000 0001 or 0000 0000,如圖: ui

若是把前面的幾位利用上,就能夠達到減小內存佔用。使用聯合體位域就能夠實現這個目的。atom

union {
    char bits;
    struct{
        char front :1;
        char back :1;
        char left :1;
        char right :1;
    };
} _walkDirection;
複製代碼

聯合體(union)中還包含了一個結構體(struct),這個結構體中的成員後面跟着的冒號和數字,數字就是表明佔用的位數(語法要求,這個要死記硬背),這個就是位域 須要注意:spa

  • 案例中的數字「1」,表明佔用1位。1位能夠表達的兩種狀況:1和0。若是想表達「0-7」的值,那麼就須要定義爲「3」操作系統

  • 位域中成員定義的位是按從低到高的方式存儲的3d

    nonuse nonuse nonuse nonuse right left back front
    0 0 0 0 1 1 1 1

上面的DZPerson能夠修改爲:

@interface DZPerson : NSObject{
    union {
        char bits;
        struct{
            char front :1;
            char back :1;
            char left :1;
            char right :1;
        };
    } _walkDirection;
}

- (void)setFront:(BOOL)isFront;
- (BOOL)isFront;

- (void)setBack:(BOOL)isBack;
- (BOOL)isBack;

@end

@implementation DZPerson

- (instancetype)init
{
    self = [super init];
    if (self) {
        _walkDirection.bits = 0b00000000;
    }
    return self;
}

- (void)setFront:(BOOL)isFront {
    if (isFront) {
        _walkDirection.bits |= kPersonWalkDirectionFrontMask;
    } else {
        _walkDirection.bits |= ~kPersonWalkDirectionFrontMask;
    }
}

- (BOOL)isFront {
    return _walkDirection.front;
}

- (void)setBack:(BOOL)isBack {
    _walkDirection.back = isBack;
}

- (BOOL)isBack {
    return _walkDirection.back;
}
@end

//調用
DZPerson *obj = [[DZPerson alloc] init];
[obj setFront:YES];
[obj setBack:YES];
NSLog(@"%@", obj.isFront ? @"YES": @"NO");
複製代碼

查看obj內存狀況,如圖: 此處內存中十六進制是0x3,由於調用代碼設置的frontback,二進制表示:0b11

isa的賦值過程


實例alloc有重要的三步驟(不清楚的能夠翻看以前的博客)

  1. 計算須要分類的空間大小:size = cls->instanceSize(extraBytes);
  2. calloc分配內存:obj = (id)calloc(1, size);
  3. 實例對象的初始化:obj->initInstanceIsa(cls, hasCxxDtor);

此處主要聊聊第三部作了什麼:

static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ...//省略不關心代碼
    size = cls->instanceSize(extraBytes);
    ...//省略不關心代碼 
    obj = (id)calloc(1, size);
    ...//省略不關心代碼
    
    //核心入口,加星標記🦍🦍🦍🦍🦍🦍🦍🦍 
    obj->initInstanceIsa(cls, hasCxxDtor);
    //🦍🦍🦍🦍🦍🦍🦍🦍 
    
    ...//省略不關心代碼
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}

⏬⏬⏬

inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) {
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

⏬⏬⏬

inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) { 
    ASSERT(!isTaggedPointer()); 
    
    if (!nonpointer) {//傳入參數是true,不會走這個分支
        ......
    } 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;
    }
}
複製代碼

核心研究的地方就是對isa中的位進行賦值,注意類的相關信息賦值在newisa.shiftcls位中。

簡要流程圖:

st=>start: isa_t newisa(0);
cond=>condition: SUPPORT_INDEXED_ISA
op1=>operation: arm64中isa的位賦值
op2=>operation: x86_64中isa的位賦值
e=>end
st->cond
cond(yes)->op1->e
cond(no)->op2->e
複製代碼

擴展 - 獲取isa中的類信息

runtime的api中提供了獲取對象類方法object_getClass,接下來研究一下源碼的實現:

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

⏬⏬⏬

inline Class objc_object::getIsa() {
    if (fastpath(!isTaggedPointer())) return 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.bits & ISA_MASK,這一步獲取的就是isa中shiftcls的值(ISA_MASK上面有講過,是個宏,在不一樣操做系統定義的值也不一樣)。
  • 獲得的值進行Class類型強轉,這也是爲何咱們在看相關源碼中,isa都是定義成Class類型緣由。
相關文章
相關標籤/搜索