isa 追踪与分析

312 阅读6分钟

一. isa 初始化

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    assert(!isTaggedPointer()); 
    //nonpointer 如果为 0 代表普通指针,如果为 1 代表优化后指针,位域存储更多信息
    if (!nonpointer) {
        //如果为普通指针,isa.cls 赋值 cls    
        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;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        //x86_64下 ISA_MAGIC_VALUE 0x001d800000000001
        //arm64下 0x000001a000000001
        newisa.bits = ISA_MAGIC_VALUE;
        //has_cxx_dtor为析构函数,这里标志为是否存在析构函数,如果没有释放更快
        newisa.has_cxx_dtor = hasCxxDtor;
        //shiftcls 类与元类的对象内存地址,由此可见 cls 与 isa 息息相关
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        isa = newisa;
    }
}

二. isa 结构

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

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD) 
    //ISA_BITFIELD 宏定义,如下代码块
    # if __arm64__
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
//ISA_BITFIELD共 64 位,标志位占一位nonpointer:表示是否对 isa 指针开启指针优化.0:纯isa指针,1:不止是类对象地址,isa 中包含了类信息、对象的引用计数等
                      标志位占一位has_assoc:关联对象标志位,0没有,1存在
                      标志位占一位has_cxx_dtor:该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象
                      标志位占 33 位shiftcls:存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。
                      标志位占 6 位magic:用于调试器判断当前对象是真的对象还是没有初始化的空间
                      标志位占 1 位weakly_referenced:标志对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放
                      标志位占 1 位deallocating:标志对象是否正在释放内存
                      标志位占 1 位has_sidetable_rc:当对象引用技术大于 10 时,则需要借用该变量存储进位
                      标志位占 19 位:当表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到下面的 has_sidetable_rc
#   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

通过objc_object我们可以追踪到 isa 其本质结构就是联合体.联合体共用内存.


三. isa 关于shiftcls的流程

首先创建一个类,并打上断点追踪.( 此环境是在 objc750 下修改的 MacOS 项目,感谢 Cooci 老师 )

//代码示例
LGPerson *object = [LGPerson alloc];
NSLog(@"Hello, World! %@",object); //断点处
//lldb 断点追踪
(lldb) x/4gx object  //查看 object 对象内存结构
0x100f03590: 0x001d800100001139 0x0000000000000000
0x100f035a0: 0x0000000000000015 0x00007fff8c0c3df0
(lldb) p/x LGPerson.class  //查看LGPerson.class 类内存结构
(Class) $1 = 0x0000000100001138 LGPerson
(lldb) p/t 0x0000000100001138  //LGPerson 16 进制转 2 进制
(long) $2 = 0b0000000000000000000000000000000100000000000000000001000100111000
(lldb) p/t 0x001d800100001139  //object对象 16 进制转 2 进制
(long) $3 = 0b0000000000011101100000000000000100000000000000000001000100111001
(lldb) p/t $3>>3<<3  //object 右移 3 左移 3 得到结果 $4
(long) $4 = 0b0000000000011101100000000000000100000000000000000001000100111000
(lldb) p/t $4<<17>>17  //$4 左移 17 右移 17 得到 $5, $5 = $2
(long) $5 = 0b0000000000000000000000000000000100000000000000000001000100111000
(lldb) 

综上所述我们可以总结

  • 对象第一个属性必然是isa
  • 通过 isa 我们可以 object 找到 class

四. isa 走位

首先还是初始化类,lldb 调试

//代码示例
LGPerson *person = [LGPerson alloc];
(lldb) x/4gx person
0x100f3b150: 0x001d8001000023b5 0x0000000000000000
0x100f3b160: 0x0000000000000000 0x0000000000000000
(lldb) p/x LGPerson.class
(Class) $1 = 0x00000001000023b0 LGPerson
(lldb) p/x 0x00000001000023b0 & 0x0000000ffffffff8
(long) $2 = 0x00000001000023b0
(lldb) po 0x00000001000023b0
LGPerson
//由 person 得到 LGPerson 通过 isa
(lldb) x/4gx LGPerson.class
0x1000023b0: 0x001d800100002389 0x0000000100b37140
0x1000023c0: 0x00000001003da290 0x0000000000000000
(lldb) po 0x1000023b0
LGPerson
//得到LGPerson内存结构,打印目标首地址
(lldb) x/4gx LGPerson.class
0x1000023b0: 0x001d800100002389 0x0000000100b37140
0x1000023c0: 0x00000001003da290 0x0000000000000000
(lldb) p/x 0x001d800100002389 & 0x0000000ffffffff8
(long) $8 = 0x0000000100002388
(lldb) po 0x0000000100002388
LGPerson

(lldb) x/4gx 0x0000000100002388
0x100002388: 0x001d800100b370f1 0x0000000100b370f0
0x100002398: 0x000000010104ee30 0x0000000400000007
(lldb) p/x 0x001d800100b370f1 & 0x0000000ffffffff8
(long) $10 = 0x0000000100b370f0
(lldb) po 0x0000000100b370f0
NSObject

(lldb) x/4gx 0x0000000100b370f0
0x100b370f0: 0x001d800100b370f1 0x0000000100b37140
0x100b37100: 0x000000010123d3e0 0x0000000400000007
(lldb) p/x 0x001d800100b370f1 & 0x0000000ffffffff8
(long) $12 = 0x0000000100b370f0
(lldb) po 0x0000000100b370f0
NSObject
//(Class)(isa.bits & ISA_MASK);所以可通过 isa 一直追踪,得到NSObject
  • 综上所述我们可以得到
  • 在内存中对象可以创建多个
  • 类只可以创建一个
  • isa 走向:对象->元类 ->根元类->根元类
  • NSObject的父类是nil,根元类的父类是NSObject

官方指向流程图