作为一个 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.读取对象的内存并格式化输出, 然后查看类信息.
- 重要的命令
$ 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