前言
通过前面学习得知,万物皆对象,类有isa指针,那么类的isa又指向哪里呢?
一、元类探索
案列分析如下
根据调试过程,为什么图中的
po 0x0000000100008360与po 0x0000000100008338中的类信息打印出来都指向LGPerson?
补充:x/4gx看内存信息p/x看指针地址
猜想:类和对象,可以无限开辟,内存不止一个类?执行以下部分代码,继续分析 ^_^
Class class1 = [LGPerson class];
Class class2 = [LGPerson alloc].class;
Class class3 = object_getClass([LGPerson alloc]);
Class class4 = [LGPerson alloc].class;
NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);
打印结果如下:
0x100008360-
0x100008360-
0x100008360-
0x100008360
元类(METACLASS)诞生
结果可以看出0x0000000100008338不是类信息,那么它是什么呢?
0x0000000100008360是p对象的isa指针0x011d800100008365 & 0x00007ffffffffff8得到结果是创建p的类LGPerson0x0000000100008338是isa中获取的类信息所指的类的isa的指针地址,既LGPerson类的类的isa指针地址,简称为元类(METACLASS)元类(METACLASS),是系统编译器自动创建的- 执行流程:
对象->isa->类、类的isa->元类
二、元类 & isa走位 分析
1. isa走位
对象、类、元类都有isa,具体isa走位流程是如何的呢?创建LGPerson对象结合lldb进行探索如下:
-
结果分析
对象的isa指向类类的isa指向元类元类的isa指向根元类(NSObject)根元类(NSObject)的isa指向它自己根元类(NSObject)
内存中只存在存在一份根元类
NSObject,根元类的元类是指向它自己isa探索元类的走位图
2. 类、元类 继承链 分析
引入以下代码分析 继承链关系,其中对象关系:LGTeacher <- LGPerson <- NSObject
#pragma mark **- NSObject 元类链**
void lgTestNSObject(void){
// NSObject实例对象
NSObject *object1 = [NSObject alloc];
// NSObject类
Class class = object_getClass(object1);
// NSObject元类
Class metaClass = object_getClass(class);
// NSObject根元类
Class rootMetaClass = object_getClass(metaClass);
// NSObject根根元类
Class rootRootMetaClass = object_getClass(rootMetaClass);
NSLog(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
// LGPerson元类
Class pMetaClass = object_getClass(LGPerson.class);
Class psuperClass = class_getSuperclass(pMetaClass);
NSLog(@"%@ - %p",psuperClass,psuperClass);
// LGTeacher -> LGPerson -> NSObject
// 元类也有一条继承链
Class tMetaClass = object_getClass(LGTeacher.class);
Class tsuperClass = class_getSuperclass(tMetaClass);
NSLog(@"%@ - %p",tsuperClass,tsuperClass);
// NSObject 根类特殊情况
Class nsuperClass = class_getSuperclass(NSObject.class);
NSLog(@"%@ - %p",nsuperClass,nsuperClass);
// 根元类 -> NSObject
Class rnsuperClass = class_getSuperclass(metaClass);
NSLog(@"%@ - %p",rnsuperClass,rnsuperClass);
}
运行结果
-
isa走位分析LGPerson元类的走位链:person(对象)——>LGPerson(类)——>LGPerson(元类)——>NSObject(根元类)——>NSObject(自己类)
-
superclass走位链分析LGPerson类的继承关系链:LGPerson(子类)-->NSObject(根类)-->nullLGTeacher类的继承关系链:LGTeacher(子类)-->LGPerson(父类)-->NSObject(根类)-->null
superclass走位图分析:
根据以上各种验证分析,对象、类、元类、根元类的关系图如下:
总结
isa走位说明
实例对象(Instance of Subclass)的isa指向类(Class)类(Class)的isa指向元类(Meta Class)元类(Meta Class)的isa指向根元类(Root Meta Class)根元类(Root Meta Class)的isa指向它自己
superclass 继承链说明
-
类的继承关系
类(Subclass)继承自父类(Superclass)父类(Superclass)继承自根类(Rootclass)根类(Rootclass)继承自nil。根类(NSObject)可以理解万物起源,无中生有
-
元类的继承关系
-
类的元类(Subclass meta)继承自父类的元类(Superclass meta) -
父类的元类(Superclass meta)继承自根类的元类(Root class meta) -
根类的元类(Root class meta)继承自根类(Root class),既NSObject
-
三、补充知识 - 指针和内存平移
1. 普通指针
// 普通指针
int a = 10; //
int b = 10; //
LGNSLog(@"%d -- %p",a,&a);
LGNSLog(@"%d -- %p",b,&b);
打印结果如下图
a、b都指向10,但是a、b的地址不一样 ,这是copy,也叫值拷贝
a、b的地址之间相差4个字节,取决于a、b的类型int
指针指向关系图如下:
2. 对象指针
// 对象 -
LGPerson *p1 = [LGPerson alloc];
LGPerson *p2 = [LGPerson alloc];
LGNSLog(@"%@ -- %p",p1,&p1);
LGNSLog(@"%@ -- %p",p2,&p2);
打印结果如下图:
当前的
p1、p2地址不同,空间也不相同
p1是指向[LGPerson alloc]创建的空间地址(内存地址),p2同理
&p1、&p2是指向p1、p2对象的指针地址,既二级指针
对象指针关系图
3. 数组指针
// 数组指针
int c[4] = {1,2,3,4};
int *d = c;
NSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
NSLog(@"%p - %p - %p",d,d+1,d+2);
for (int i = 0; i<4; i++) {
int value = *(d+i);
NSLog(@"%d",value);
}
打印结果
&c、&c[0]都是数组的首地址
&c[0]、&c[1]相差4字节,地址之间相差字节数根据当前的数据类型int
首地址(&c)+偏移量(1)可取出数组中其他元素,偏移量既数组的下标。首地址+偏移量获取元素的内存地址=偏移量 * 数据类型字节数+首地址
四、类结构分析
在上一篇文章iOS底层原理 03:OC对象初始化之本质与iSA中,使用clang编译过main.m文件,从中可以得出:
NSObject同样指向structobjc_object的结构体isa的类型为Class,被定义为指向objc_class的结构体指针
1. objc_class 分析
-
在
objc源码中搜索objc_class定义,得到有两个地方的定义: -
runtime.h 文件中,
objc_class定义,由于执行流程是非__OBJC2__,已弃用。
- objc_runtime-new.h,
objc_class定义,现阶段阶段正在使用,如下图所示。
从源码的定义中可以看出,objc_class结构体类型是继承自objc_object的。
-
属性分析:
isa:继承自objc_object的isa,所以objc_class也拥有了isa,(结构体指针)8字节superclass:Class 类型,Class由objc_object定义,8字节cache:cache_t类型,而cache_t是结构体,并且结构体大小需要内部的属性来确定。bits:class_data_bits_t类型,求解地址:首地址+ (isa+superclasscache),首地址经过3个属性内存大小总和的平移。
2. 求解cache类型的内存大小
- 进入
cache_t的定义。排除其中的方法函数(方法不在结构体区域内存中),得出以下几个属性
-
_bucketsAndMaybeMask: 看uintptr_t类型,而uintptr_t是unsigned long定义的,占8字节 -
union:联合体结构,其中有2个数据,根据联合体的情况,只去其一struct:是结构体定义,根据图中所示,占8字节_originalPreoptCache: 带*号类型,占8字节
-
总结:
cache的内存大小 =8 + 8 = 16字节,原因:union是互斥关系,只取一瓢。
3. 获取 bits 内容
- 上一步已计算出
cache的内存大小,想要获取bits内容,需要将首地址平移32字节即可
以下是通过lldb命令调试获取bits的过程
获取首地址
(lldb) p/x LGPerson.class
(Class) $0 =0x0000000100008370
(lldb) x/4gx LGPerson.class
0x100008370: 0x0000000100008398 0x0000000100372140
0x100008380: 0x000000010100bf90 0x0002802000000003
p/x LGPerson.class直接获取x/4gx LGPerson.class通过内存信息获取
4. 属性列表(properties)分析
根据上一步分析,获取数据内容已进入class_rw_t结构,分析当前class_rw_t结构,发现当前结构体中相应的方法获取 属性列表(properties)、方法列表(methods)
- 准备工作,添加 LGPerson对象 如下:
@interface LGPerson : NSObject
{
NSString *subject;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;
#import "LGPerson.h"
@implementation LGPerson
- (instancetype)init{
if (self = [super init]) {
self.name = @"Cat";
}
return self;
}
@end
通过class_rw_t提供的方法,继续分析bits中的属性列表,以下是lldb分析流程
p $5.properties()中的properties方法是由class_rw_t提供的。- 通过
count = 2可知,当前属性列表含有2个字段。p $9.get(0)、p $9.get(1)分别获取到了name、hobby字段。 - 属性列表
(properties)中无成员变量(subject)
5.方法列表(methods)分析
- 准备工作,在上一步的
LGPerson对象中添加2个方法(实例方法&类方法)
@interface LGPerson : NSObject
{
NSString *subject;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;
- (void)sayNB;
+ (void)say666;
#import "LGPerson.h"
@implementation LGPerson
- (instancetype)init{
if (self = [super init]) {
self.name = @"Cat";
}
return self;
}
- (void)sayNB{
}
+ (void)say666{
}
@end
通过class_rw_t方法,查找bits中的方法列表,lldb分析流程如下:
-
p $5.methods()获取方法列表的list结构,分析源码可知,methods方法是由class_rw_t提供的,const method_array_t methods() const -
通过
lldb调试可知,count = 6,说明当前对象中含有6个方法,但是,输出第一个元素打印出来为空???-
进一步源码分析,通过
method_array_t方法层层推进,得出以下逻辑链:method_array_t->method_list_t->method_t,最终在method_t结构体中找到big结构体,
-
-
p $p.get(0).big()打印添加 big()方法继续打印,结果如下:
-
可以看出,通过
p $9.get(i).big()内存偏移的方式,6个方法成功输出。 -
方法类型如下:
实例方法(sayNB)、name habby系统生成的get/set方法、init方法 -
6 个方法中无
+ (void)say666;,method_array_t中只有 实例方法,没有类方法,
6. 成员变量(ivars)分析
有上面的属性列表分析,得出propertys 中只有属性,没有成员变量, 那么成员变量存储在哪里呢?
分析 class_rw_t的定义发现,当前里面无存取成员变量的方法,但是在 ro() const方法中,进入ro()方法的 class_ro_t定义,在其中发现有一个 ivars属性,可猜测成员变量存在ivars属性中呢?
以下是从ro()方法中,通过lldb调试寻找突破口:
-
通过lldb调试发现,获取
ivars属性,其中count = 3,除了包括成员变量,还有属性定义的成员变量也在ivars中。 -
对象
{ NSString *subject }定义的成员变量,会存储在bits属性中,查找流程:bits->data()->ro()->ivars,
7. 类方法分析
方法列表(methods)只有实例方法,无类方法,那么类方法存储在哪里呢?
前文,提及了元类,类对象的isa指向元类。对象的方法存储在类的bits中,那么类的方法是否可能存储在元类的bits中呢?可通过lldb调试验证这个猜想,如下图所示:
通过图中元类方法列表的打印结果可知,上面的猜想验证通过。
总结:
类的类方法存储在元类的bits属性中,- 获取类方法流程:
类-->元类-->元类bits-->data()-->methods()-->list-->ptr-->get(i).big()