美文网首页
OC对象底层探索 — isa的初始化和指向分析

OC对象底层探索 — isa的初始化和指向分析

作者: Dezi | 来源:发表于2020-03-08 22:40 被阅读0次

    用于记录iOS底层学习,以备后续回顾

    OC对象底层探索

    前言

    通过之前的探索,我们了解了对象是怎么创建的,对象的内存是如何分配的。而探索OC对象就避免不了要了解isa,下面我们结合源码对isa进行详细的分析。

    一、isa的初始化及其本质

    1.1 isa的初始化

    对象初始化的alloc方法内部,会调用initIsa方法。

    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) {
            isa.cls = 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;
            // 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;
        }
    }
    

    对象通过isa 关联类。
    nonpointer = true做初始化的赋值;!nonpointer直接赋值cls。

    1.2 isa的本质

    a. isa是什么

    isa:实际是一个isa_t结构,看源码可知,isa是一个联合体,在类中以Class 对象存在,用来指向类的地址,大小为 8 个字节,也就是 64 位。
    联合体的特性:内存共用,或者说带有互斥特性,意思就是赋值了cls,就不对其他成员赋值了。

    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
    };
    
    b. ISA_BITFIELD组成及各成员是用来做什么的

    我们基于arm64架构来解读ISA_BITFIELD,一共为8字节64位:

    #   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)
    
    • nonpointer表示是否对isa指针开启优化(我们现在的都是开启了优化),值为0:纯isa指针; 值为1:不止是类对象地址,isa中还包含了类信息,对象的引用计数等;
    • has_assoc是否有关联对象,值0无,值1有。
    • has_cxx_dtor是否有c++或者objc析构函数,如果有析构函数,先走析构逻辑,没有就更快的释放对象。
    • shiftcls存储类指针的值,开启指针优化的时候,在arm64架构中有33位存储类指针。
    • magic用来调试器判断当前对象是真的对象还是没有初始化的空间。
    • weakly_referenced标志对象是否指向或者曾经指向一个ARC的弱变量,没有弱引用的对象可以更快的释放。
    • deallocating标志对象是否正在释放。
    • has_sidetable_rc当对象的引用计数大于10的时候,会借用该变量存储进位。
    • extra_rc表示该对象的引用计数的值,实际上是引用计数的值减去1。例如:该对象的引用计数为10,则该变量的值为9,若超过10,则需要用到has_sidetable_rc

    二、isa指向分析

    内存里面,对象的属性位置是会发生变化的,但对象的第一个属性必然是isa。

    代码段:

    #import <Foundation/Foundation.h>
    #import <objc/runtime.h>
    #import "DZPerson.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            DZPerson *object = [DZPerson alloc];
            object_getClass(object);
            NSLog(@"Hello! %@",object);
        }
        return 0;
    }
    

    2.1、isa关联对象和类

    使用object_getClass(object)方法分析对象是如何关联类的:

    Class object_getClass(id obj)
    {
        if (obj) return obj->getIsa();
        else return Nil;
    }
    
    inline Class 
    objc_object::getIsa() 
    {
        if (!isTaggedPointer()) return ISA();
    
        uintptr_t ptr = (uintptr_t)this;
        if (isExtTaggedPointer()) {
            uintptr_t slot = 
                (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
            return objc_tag_ext_classes[slot];
        } else {
            uintptr_t slot = 
                (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
            return objc_tag_classes[slot];
        }
    }
    
    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
    }
    
    #   define ISA_MASK        0x00007ffffffffff8ULL
    

    由以上源码可知:对象通过(Class)(isa.bits & ISA_MASK) 方法得到类地址。
    我们来亲自验证:

    isa.bits & ISA_MASK

    根据上述结果,我们可以确定对象是通过isa关联类的。

    2.2、isa指向

    lldb命令

    • x/4gx objc打印objc的4段内存信息。扩展:x/6gx就是打印6段内存信息。
    • p/t 打印二进制信息,p/o打印八进制信息,p/x打印十六进制信息。

    我们通过(Class)(isa.bits & ISA_MASK)方法,使用lldb一级一级的调试获取指向信息:

    isa指向

    由上图分析可得:

    • 实例对象的isa指向类
    • 类的isa指向元类
    • 元类的isa指向根元类
    • 根元类的isa指向自己

    下面我们再来看看官方给出的isa指向图:

    官方isa指向图

    虚线代表了isa的指向:实例对象 -> 类 -> 元类 -> 根元类 -> 根元类本身。需要注意的是只有根元类会指向自身类。
    实线代表了继承关系:DZTeacher -> DZPerson -> NSObject -> nil。这里需要注意的是根元类的父类是NSObject,NSObject的父类是nil。

    相关文章

      网友评论

          本文标题:OC对象底层探索 — isa的初始化和指向分析

          本文链接:https://www.haomeiwen.com/subject/tsslqhtx.html