目录:
- 1:isa指向与元类的superclass指向分析
- 1.1 isa掩码(ISA_MASK)介绍
- 1.2 isa指向分析
- 1.3 元类superclass指向分析
- 2:类的结构分析
- 2.1 ObjC 1.0的objc_class(已弃用)
- 2.2 ObjC 2.0的objc_class(基于objc4-818.2)
- 2.2.1 objc-runtime-old.h文件里objc_class的定义
- 2.2.2 objc-runtime-new.h文件里objc_class的定义
- 2.3 objc_class成员变量bits的分析
- 2.3.1 内存偏移
1:isa
指向与元类的superclass指向分析
书接上文OC底层原理初探之对象的本质(三)alloc探索下继续对isa
进行探索。身为iOS开发者都知道对象的isa
指向类,那么今天就来分析下isa
的走位图。
1.1 isa
掩码(ISA_MASK
)介绍
上文OC底层原理初探之对象的本质(三)alloc探索下中已经介绍过ISA_MASK
及其使用方法,接下来我们的探索需要借助ISA_MASK
,现行源码版本一共分为三种架构:
x86_64: define ISA_MASK 0x00007ffffffffff8ULL // ULL: unsigned long long 无符号长整形
arm64: define ISA_MASK 0x0000000ffffffff8ULL
arm64(simulators): define ISA_MASK 0x007ffffffffffff8ULL
复制代码
1.2 isa
指向分析
代码示例:
#import <Foundation/Foundation.h>
#import "XJPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
XJPerson *p = [XJPerson alloc];
NSLog(@"%@",p);
}
return 0;
}
********************** lldb调试结果 **********************
(lldb) x/4gx p // 16进制格式化输出对象p的内存信息
0x10044f160: 0x001d800100008365 0x0000000000000000 // 得到首地址的isa
0x10044f170: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x001d800100008365 & 0x00007ffffffffff8 // 用前文的方式,isa & ISA_MASK
(long) $1 = 0x0000000100008360 // 得到类的地址信息
(lldb) po 0x0000000100008360 // po 输出
XJPerson // 得到XJPerson
// 继续探索
(lldb) x/4gx 0x0000000100008360 // 16进制格式化输出XJPerson类的内存信息
0x100008360: 0x0000000100008338 0x00007fff932d8118
0x100008370: 0x00007fff6bbb6140 0x0000802c00000000
(lldb) p/x 0x0000000100008338 & 0x00007ffffffffff8 // 用首地址 & ISA_MASK
(long) $3 = 0x0000000100008338 // 得到一个新的地址
(lldb) po 0x0000000100008338 // po 输出
XJPerson // 还是可以得到XJPerson
复制代码
很显然,两次输出XJPerson
的地址不一样,分别为0x0000000100008360
和0x0000000100008338
,这里不禁产生了一个疑问,难道类在运行时会类似对象一样创建多份吗?接下来验证一下:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "XJPerson.h"
//MARK: - 分析类对象内存存在个数
void xjTestClassNum(void){
Class class1 = [XJPerson class];
Class class2 = [XJPerson alloc].class;
Class class3 = object_getClass([XJPerson alloc]);
Class class4 = [XJPerson alloc].class;
NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
xjTestClassNum();
}
return 0;
}
********************** 打印输出结果 **********************
2021-06-19 15:39:55.257537+0800 002-isa分析[5557:148166]
0x100008360-
0x100008360-
0x100008360-
0x100008360
Program ended with exit code: 0
复制代码
从上面案例可以看出,不管生成多少个对象,类始终只有一个,那么0x0000000100008338
这个地址po
输出为什么也是XJPerson
呢?下面用MachOView
读取运行文件看看究竟:
在MachOView
搜索class
除了能看到_OBJC_CLASS_$_XJPerson
外,还能看到_OBJC_METACLASS_$_XJPerson
,这就是元类(MetaClass)
。我们代码中并没有主动生成元类
,说明这是程序在运行时主动帮我们生成的。
到这里已经知道对象的isa
->类对象,类对象的isa
->元类(MetaClass),继续深入探索:
// 省略一些之前代码,并添加XJTeacher类的实例对象的创建代码,XJTeacher继承自XJPerson
// 实例化XJTeacher类的对象
XJTeacher *t = [XJTeacher alloc];
NSLog(@"%@",t);
********************** lldb调试结果 **********************
// XJPerson lldb 调试
// 省略一些之前XJPerson类lldb调试内容
(lldb) po 0x0000000100008338 // XJPerson类的元类
XJPerson
(lldb) x/4gx 0x0000000100008338 // 16进制格式化输出XJPerson元类的内存信息
0x100008338: 0x00007fff932d80f0 0x00007fff932d80f0 // 首地址为isa
0x100008348: 0x0000000100406c50 0x0003e03500000007
(lldb) p/x 0x00007fff932d80f0 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $5 = 0x00007fff932d80f0 // 得到新的地址
(lldb) po 0x00007fff932d80f0 // po 输出
NSObject // 输出为NSObject,那么这是类还是元类呢
// XJTeacher lldb 调试
(lldb) x/4gx t // 16进制格式化输出对象t的内存信息
0x100522330: 0x001d800100008315 0x0000000000000000 // 首地址为isa
0x100522340: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x001d800100008315 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $1 = 0x0000000100008310 // 得到新的地址
(lldb) po 0x0000000100008310 // po 输出
XJTeacher // 输出为XJTeacher,即类XJTeacher
(lldb) x/4gx 0x0000000100008310 // 16进制格式化输出类XJTeacher的内存信息
0x100008310: 0x00000001000082e8 0x0000000100008360 // 首地址为类XJTeacher的isa
0x100008320: 0x00007fff6bbb6140 0x0000802c00000000
(lldb) p/x 0x00000001000082e8 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $3 = 0x00000001000082e8 // 得到新的地址
(lldb) po 0x00000001000082e8 // po 输出
XJTeacher // 输出为XJTeacher,即元类XJTeacher
(lldb) x/4gx 0x00000001000082e8 // 16进制格式化输出元类XJTeacher的内存信息
0x1000082e8: 0x00007fff932d80f0 0x0000000100008338 // 首地址为元类XJTeacher的isa
0x1000082f8: 0x00000001006433a0 0x0003e03500000007
(lldb) p/x 0x00007fff932d80f0 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $5 = 0x00007fff932d80f0
(lldb) po 0x00007fff932d80f0
NSObject // 输出为NSObject,那么这是类还是元类呢
// NSObject lldb 调试
// 下面验证一下
(lldb) p/x [NSObject class] // 16进制输出NSObject的类的内存信息
(Class) $7 = 0x00007fff932d8118 NSObject // 得到根类NSObject的地址
(lldb) x/4gx 0x00007fff932d8118 // 16进制格式化输出根类NSObject的内存信息
0x7fff932d8118: 0x00007fff932d80f0 0x0000000000000000 // 首地址为根类NSObject的isa
0x7fff932d8128: 0x00000001004992d0 0x0001801000000003
(lldb) p/x 0x00007fff932d80f0 & 0x00007ffffffffff8 // isa & ISA_MASK
(long) $8 = 0x00007fff932d80f0 // 得到新的地址
(lldb) po 0x00007fff932d80f0 // po 输出
NSObject // 输出为NSObject,根类的元类,即根元类(RootMetaClass)
// 继续验证
(lldb) x/4gx 0x00007fff932d80f0
0x7fff932d80f0: 0x00007fff932d80f0 0x00007fff932d8118
0x7fff932d8100: 0x0000000100643750 0x0005e03100000007
(lldb) p/x 0x00007fff932d80f0 & 0x00007ffffffffff8
(long) $18 = 0x00007fff932d80f0
(lldb) po 0x00007fff932d80f0
NSObject
复制代码
图解:
结论:
- 非根类
NSObject
类的实例对象的isa
指向类(Class
),类的isa
指向元类(MetaClass
),元类的isa
指向根元类(RootMetaClass
),而不是父类的元类。 - 根类
NSObject
类的实例对象的isa
指向根类(RootClass
),根类的isa
指向根元类(RootMetaClass
)。 - 根元类的
isa
还是指向根元类。
1.3 元类superclass
指向分析
代码实例:
#pragma mark - 元类的superclass链
void xjSuperclass(void) {
//NSObjcet实例对象
NSObject *objectInstance = [NSObject alloc];
//NSObject类
Class object = object_getClass(objectInstance);
//NSobject元类
Class metaClass = object_getClass(object);
//NSObjct根元类
Class rootMetaClass = object_getClass(metaClass);
NSLog(@"\n%p NSObject实例对象\n%p NSObject类\n%p NSObject元类\n%p NSObject根元类\n",objectInstance,object,metaClass,rootMetaClass);
// XJPerson的元类
Class personMetaClass = object_getClass(XJPerson.class);
NSLog(@"XJPerson的元类 : %@ - %p",personMetaClass, personMetaClass);
// XJPerson元类的父类
Class personSuperMetaclass = class_getSuperclass(personMetaClass);
NSLog(@"XJPerson元类的父类 : %@ - %p",personSuperMetaclass, personSuperMetaclass);
// XJTeacher的元类
Class teacherMetaClass = object_getClass(XJTeacher.class);
NSLog(@"XJTeacher的元类 : %@ - %p",teacherMetaClass,teacherMetaClass);
// XJTeacher元类的父类
Class childrenSuperMetaClass = class_getSuperclass(teacherMetaClass);
NSLog(@"XJTeacher元类的父类 : %@ - %p",childrenSuperMetaClass,childrenSuperMetaClass);
// NSObject类的父类
Class superObject = class_getSuperclass(NSObject.class);
NSLog(@"NSObject的父类 : %@ - %p",superObject,superObject);
//NSObject根元类的父类
Class rootSuperMetaObject = class_getSuperclass(rootMetaClass);
NSLog(@"NSObject根元类的父类 : %@ - %p",rootSuperMetaObject,rootSuperMetaObject);
}
********************** 打印输出结果 **********************
2021-06-19 23:10:35.443934+0800 002-isa分析[12886:361372]
0x100644850 NSObject实例对象
0x7fff932d8118 NSObject类
0x7fff932d80f0 NSObject元类
0x7fff932d80f0 NSObject根元类
2021-06-19 23:10:35.444578+0800 002-isa分析[12886:361372] XJPerson的元类 : XJPerson - 0x100008338
2021-06-19 23:10:35.444650+0800 002-isa分析[12886:361372] XJPerson元类的父类 : NSObject - 0x7fff932d80f0
2021-06-19 23:10:35.444800+0800 002-isa分析[12886:361372] XJTeacher的元类 : XJTeacher - 0x1000082e8
2021-06-19 23:10:35.444838+0800 002-isa分析[12886:361372] XJTeacher元类的父类 : XJPerson - 0x100008338
2021-06-19 23:10:35.444908+0800 002-isa分析[12886:361372] NSObject的父类 : (null) - 0x0
2021-06-19 23:10:35.444942+0800 002-isa分析[12886:361372] NSObject根元类的父类 : NSObject - 0x7fff932d8118
复制代码
结论:
- 元类之间也存在继承链,与类一样。
- 根元类的父类是根类。
- 根类的父类是nil。
图解:
2:类的结构分析
本小节将对类的结构进行分析,前面探索过类在底层实现就是objc_class
结构体,源码里搜索struct objc_class
,会发现在objc4-818.2
源码里有三处定义:
2.1 ObjC 1.0的objc_class
(已弃用)
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
复制代码
2.2 ObjC 2.0的objc_class
(基于objc4-818.2
)
2.2.1 objc-runtime-old.h
文件里objc_class
的定义
struct objc_class : objc_object {
Class superclass;
const char *name;
uint32_t version;
uint32_t info;
uint32_t instance_size;
struct old_ivar_list *ivars;
struct old_method_list **methodLists;
Cache cache;
struct old_protocol_list *protocols;
// CLS_EXT only
const uint8_t *ivar_layout;
struct old_class_ext *ext;
// 篇幅原因,省略大部分代码
}
复制代码
2.2.2 objc-runtime-new.h
文件里objc_class
的定义
// 现行版本所使用的,我们的探索也是基于这个定义
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// Class ISA; // 8字节,隐藏变量isa,继承自objc_object
Class superclass; // 8字节
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
// 篇幅原因,省略大部分代码
}
复制代码
图解:
从objc-runtime-new.h
文件里objc_class
的定义可以看出一共有4个成员变量,分别为isa
(继承自objc_object
的隐藏变量)、superclass
、cache
和bits
,其中isa
和superclass
已经分析过了,这里不在赘述,查看cache
和bits
的注释和数据类型定义,终于在struct class_rw_t
里发现了方法、属性和协议的相关信息,接下来我们就对bits
进行相关探索、验证。
2.3 objc_class
成员变量bits
的分析
已知类名,想要拿到成员变量bits
,需要通过内存偏移的方式,下面就先简单介绍下内存偏移。
2.3.1 内存偏移
案例:
#import <Foundation/Foundation.h>
#import "LGPerson.h"
复制代码