isa 指针走向

2022-03-15 08:09:17 浏览数 (1)

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

isa 定义

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

__arm64__ 真机的宏定义

代码语言:javascript复制
#     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 的宏定义

代码语言:javascript复制
#   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)

(滑动显示更多)

代码语言:javascript复制
union isa_t {
uintptr_t bits;
private:
    Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
        ISA_BITFIELD; 
    };
#endif
};

(滑动显示更多)

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

这个值怎么取呢?

只要让 isa 的值 和 ISA_MASK 进行 与 运算即可.

代码语言:javascript复制
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指针 和 继承关系的走向图

举例来验证 isa 指针走向

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

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

创建两个类, 分别是 Person Teacher, Teacher继承自Person.

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

代码语言:javascript复制
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;
}

(滑动显示更多)

代码图和类信息的输出

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

  • 重要的命令
代码语言:javascript复制
$ x/4gx teacher
$ p/x 0x011d800100008189 & 0x00007ffffffffff8ULL
$ po 0x0000000100008160

(滑动显示更多)

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

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

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

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

继承关系走向

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

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

总结

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

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

  • 对象 -> 类 -> 元类 -> 根元类 <-> 根元类
  • 特殊: NSObject 对象 -> NSObject类 -> NSObject元类(根元类) <-> 根元类

2.继承关系走向, 根元类的父类指向 NSObject, 这就是万物皆对象

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

特别注意的是 isa 只会从对象找到类, 再到元类, 然后直接到根元类.

不仅类之间存在继承关系, 元类之间也存在继承关系.

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

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

作者:Andy_GF

链接:https://juejin.cn/post/7043717006901641229

0 人点赞