isa 指针走向

2,996 阅读3分钟

作为一个 iOSer, 都知道NSObject 是基类, 肯定都听说过一句话: 万物皆对象, NSObject 类的第一个成员就是 isa 指针, 这个就不展示源码了, 这个指针存着类的很多信息, 而不仅仅是指向类内存的指针.

isa 定义

isa 指针的底层原本定义如下, 只看成员, 不看方法;

__arm64__ 真机的宏定义

#     define ISA_MASK        0x0000000ffffffff8ULL
#     define ISA_MAGIC_MASK  0x000003f000000001ULL
#     define ISA_MAGIC_VALUE 0x000001a000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 1
#     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 unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19
#     define RC_ONE   (1ULL<<45)
#     define RC_HALF  (1ULL<<18)

__x86_64__ macOS 的宏定义

#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_HAS_CXX_DTOR_BIT 1
#   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 unused            : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)
union isa_t {
    uintptr_t bits;
private:
    Class cls;
public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD; 
    };
#endif
};

原本的定义比较不太好看, 我给他改造一下, 整合在一起, 以 macOS 为例, 下面将会用 macOS 项目进行举例, shiftcls 就是指向类的信息.

这个值怎么取呢?
只要让 isa 的值 和 ISA_MASK 进行 运算即可.

union isa_t {
    uintptr_t bits;
    Class cls;
    struct {
      uintptr_t nonpointer        : 1;                                         
      uintptr_t has_assoc         : 1;                                         
      uintptr_t has_cxx_dtor      : 1;                                         
      uintptr_t shiftcls          : 44;
      uintptr_t magic             : 6;                                         
      uintptr_t weakly_referenced : 1;                                         
      uintptr_t unused            : 1;                                         
      uintptr_t has_sidetable_rc  : 1;                                         
      uintptr_t extra_rc          : 8; 
    };
};

isa指针 和 继承关系的走向图

image.png

举例来验证 isa 指针走向

接下来我们以自定义类举例来验证这幅图的 isa 指针走向, 我们一步一步向上查找.

首先我们创建一个 macOS 平台的 Command Line Tool 项目

image.png

创建两个类, 分别是 Person Teacher, Teacher继承自Person. 主要代码如下, 在 Hello world 这一行下一个断点. 然后进行 lldb 调试, 其实这些打印也没什么用, 也可以用 lldb 输出, 这里就对比打印结果就可以. 图和代码都给出来, 图比较直观.

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Teacher *teacher = [Teacher alloc];
  
        NSLog(@"%@",teacher);
        NSLog(@"%@ - %p",teacher.class, teacher.class);
        NSLog(@"%@ - %p",[Teacher class], [Teacher class]);
        NSLog(@"%@ - %p",object_getClass(teacher), object_getClass(teacher));
        
        NSLog(@"Hello world !");
    }
    return 0;
}

代码图和类信息的输出 image.png

  1. 读取对象的内存并格式化输出, 然后查看信息.
  • 重要的命令
    $ x/4gx teacher
    $ p/x 0x011d800100008189 & 0x00007ffffffffff8ULL
    $ po 0x0000000100008160
    

image.png

  1. 读取类的内存并格式化输出, 然后查看元类信息.

image.png

  1. 读取类的内存并格式化输出, 然后查看根元类信息.

image.png

  1. 读取类的内存并格式化输出, 然后查看根元类信息. image.png

此时发现根元类isa 是指向自己的, 也就意味着 isa 走到头了.

继承关系走向

看看各自的继承关系走向, NSObject 第二个成员就是 superClass, 也就是内存的第二段, 从打印中可以看出, 不仅所有的普通类最终继承自 NSObject, 连 根元类 也继承自 NSObject, 我想这也是 万物皆对象 这名话的意义吧 😄

image.png

特殊情况:读取基类 NSObject 的内存信息, 会发现父类是 nil, isa 也指向 根元类. image.png

总结

我对这幅图理解有两点, 简单说一个是关于 isa 指针的, 一个是关于继承关系的, 继承关系这个大家应该比较熟悉, 因为接触的比较多, 几乎每天都在打交道, isa 指针就不同了, 因为平时也用不上, 属于底层原理级别的.

  1. isa指针走向, 根元类的isa指向自己

    • 对象 -> 类 -> 元类 -> 根元类 <-> 根元类
    • 特殊: NSObject 对象 -> NSObject类 -> NSObject元类(根元类) <-> 根元类
  2. 继承关系走向, 根元类的父类指向 NSObject, 这就是万物皆对象

    • 子类 -> 父类 -> 父类的父类 -> ... -> NSObject -> nil
    • 子元类 -> 父元类 -> 父元类的父类 -> ... -> 根元类 -> NSObject -> nil

特别注意的是 isa 只会从对象找到, 再到元类, 然后直接到根元类. 不仅类之间存在继承关系, 元类之间也存在继承关系.

类在内存里只存在一份, 继承关系只存在于类之间, 而不存在于对象之间;

到此, 我们的验证也结束了. 相信大家会对这幅图有一个全新的认识, 对 isa 指针也会有一个全新的认识. 感谢捧场, 来都来了, 点赞支持一下吧.