前言
通过前面学习得知,万物皆对象,类有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
的类LGPerson
0x0000000100008338
是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(根类)
-->null
LGTeacher
类的继承关系链: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
同样指向struct
objc_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
+superclass
cache
),首地址
经过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()