iOS底层-类的探索分析之isa及继承链
前言
众所周知,OC是基于C/C++扩展而来的,那么也就是说OC也是面向对象的一门语言,即然是面向对象,必然就有类与对象的概念,我们就从以下几个方面分析一下OC的类。
ISA的分析
首先在我们的demo工程中打个断点,调试一下,如图所示:
1
然后我们通过p/x person 命令查下person对象的地址,如图:
我们再用x/4gx 0x0000000100707f30 读取这个地址,得到下图:
3
"0x011d800100008365"这个就是我们的对象isa,
我们p/x 0x011d800100008365 & 0x00007ffffffffff8(isa的mask)命令执行一下,得到下图:
4
接着,我们在po 0x0000000100008360 执行这个命令,打印下这个地址。
0x0000000100008360 这个就是我们的RoPerson类。
我们再执行一下x/4gx 0x0000000100008360命令,得到下图结果:
我们再po 0x0000000100008338 & 0x00007ffffffffff8一下,发现了一个奇怪的现象,如图:
也是指向我们的RoPerson类
0x0000000100008360与0x0000000100008338 都输出了RoPerson类
这也说明了当前的类和我们的对象一样,可以无限开辟,内存中不止有一个类。
我们接着往下验证,分析。
//MARK: - 分析类对象内存存在个数
void RoTestClassNum(void){
Class class1 = [RoPerson class];
Class class2 = [RoPerson alloc].class;
Class class3 = object_getClass([RoPerson alloc]);
Class class4 = [RoPerson alloc].class;
NSLog(@"\n%p-\n%p-\n%p-\n%p",class1,class2,class3,class4);
}
我们看下这段代码,看下打印果,如图:
8
这个图可以看出来 0x0000000100008360是我们的类,而0x0000000100008338不是我们的类,那么0x0000000100008338是什么,我们知道对象有一个isa指向了类,类的isa指向了元素 这个元类在我们的代码中是没有存在的,我们用MachoView分析下我们demo的macho文件。
在我们的Symbol Table找到了RoPerson的元类,也就是说编译器帮我们生成了元类。
我们知道元类也有isa,那么它的isa又指向哪里,我们接下来分析。
我们先x/4gx 0x0000000100008338这个地址,得到下图:
我们再执行一下p/x 0x00007fff806b2360 & 0x00007ffffffffff8 得到下图:
11
我们再po 0x00007fff806b2360,得到的是NSObject,看图:
12
从上图可以看出,我们的元类的isa指向了根元类。
接着我们再执行p/x NSObject.class,得到下图
再执行x/4gx 0x00007fff806b2388命令,得到下图:
14
继续执行p/x 0x00007fff806b2360 & 0x00007ffffffffff8命令,如图所示:
15
从上图可以看出根元类的isa又指向了自己,形成了一个闭环。
它的基本流程是:
对象的isa->类----isa------>元类----isa------>根元类----isa------>自身。
我们可以看下面一张图:
继承链
我们先看下段代码
#pragma mark - NSObject 元类链
void RoTestNSObject(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);
// RoPerson元类
Class pMetaClass = object_getClass(RoPerson.class);
Class psuperClass = class_getSuperclass(pMetaClass);
NSLog(@"%@ - %p",psuperClass,psuperClass);
}
我们看下打印结果,如图:
17
从上图我们发现,NSobject的元类与根元类是同一个,RoPerson元类的父类是NSObject。
我们在上述代码中再加入以下代码
// RoTeacher -> RoPerson -> NSObject
Class tMetaClass = object_getClass(RoTeacher.class);
Class tsuperClass = class_getSuperclass(tMetaClass);
NSLog(@"%@ - %p",tsuperClass,tsuperClass);
我们再来看打印结果:
18
从上图看出RoTeacher的元类是RoPerson,RoPerson的元类是NSObject,
也就是说元类也是继承链,下图所示
19
图中可以看出:
- 类的继承: Subclass->Superclass->Root class(根类)
- 元类的继承:Subclass(meta)->Superclass(meta)->Root class(meta)
- Subclass(meta)为Subclass的元类,以此类推。
我们在上述代码再加入以下代码
// NSObject 根类特殊情况
Class nsuperClass = class_getSuperclass(NSObject.class);
NSLog(@"%@ - %p",nsuperClass,nsuperClass);
// 根元类 -> NSObject
Class rnsuperClass = class_getSuperclass(metaClass);
NSLog(@"%@ - %p",rnsuperClass,rnsuperClass);
我们再来看下打印结果:
从上图的结果我们可以得出以下结论:
根类没有父类,根元类的父类就是这个类,也就是NSObject,所有的类都来自于NSObject这就是继承链。
我们再来看下苹果官方的isa与继承链的图:
21
这幅图很好说明了isa的指向关系和继承链。
类的结构分析
我们打开objc的源码,搜下struct objc_class,如图:
21
我们从上图可以看到,objc_class继承objc_object
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
上述代码是类的结构,ISA为隐藏属性,superclass父类,class_data_bits_t 存放的是methodlist,ivarlist,property,接下来我们来分析和验证一下。
首先我们断点调试下,如图:
22
接着我们在lldb中执行一下x/4gx RoPerson.class,得到下图
0x00000001000083a8是isa在ISA分析的时候已经验证过。
0x000000010036a140是superclass,我们执行下 po 0x000000010036a140 验证,如图所示:
24
NSObject即是RoPerson的superclass。
0x0000000100758910是我们的cache,我们这里先不讲,后续推出。
我们来讲下0x0002802800000003即bits。
我们要分析bits,就要平移isa的长度+superclass的长度+cache的长度,
isa的长度=8字节(结构体指针)
superclass的长度=8字节(结构体指针)
cache的长度=16字节
我们分析下cache的长度,cache是cache_t结构体,由于在结构体中,只有成员变量占用结构体大小(方法在方法区,不占用结构体内存),所以我们只需要分析以下代码所占用的内存大小
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8字节
union {
struct {
explicit_atomic<mask_t> _maybeMask; // 4字节
#if __LP64__
uint16_t _flags; // 2字节
#endif
uint16_t _occupied; // 2字节
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache; // 8字节
};
从以上代码可以看出,cache的长度占用16字节,所以我们要平移8+8+16=32字节才能找到bits。
所以我们可以执行下p/x 0x00000001000083a8+0x20(0x20即32)这个命令,得到下图:
我们再执行下p (class_data_bits_t )0x00000001000083c8得到下图:
26
我们来看下$9的内存情况,我们执行下 p $9->data()命令data()是class_data_bits_t的方法,参考源码,得到下图:
27
我们再取$11,执行命令p $11,得到以下图:
28
接着我们执行p $3.properties()*,(重新运行项目,序号从0开始,不影响我们的验证),properties()是class_rw_t结构体中的方法,我们可以得到下图:
29
接着我们要取出list,所以执行p $4.list命令,得到下图:
30
接着我们再来执行p $5.ptr命令,得到下图
31
再执行p *$6,得到下图:
32
我们取一下property_list的内容,执行p $7.get(0)命令,如图所示:
33
取到了,我们的name属性。
由于RoPerson中只有一个name属性,我们加一些代码,我把RoPerson的代码贴出来,如下所示:
.h文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface RoPerson : NSObject{
NSString *subject;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;
- (void)sayNB;
+ (void)say666;
@end
NS_ASSUME_NONNULL_END
.m文件
#import "RoPerson.h"
@implementation RoPerson
- (instancetype)init{
if (self = [super init]) {
self.name = @"robert";
}
return self;
}
- (void)sayNB{
}
+ (void)say666{
}
@end
重新运行我们的项目,然后断点和lldb调试,如图所示:
34
name和hobby属性都取到了,而subject没有取到(我们这里先不提,后续补上),我们再看下methods,执行一系列lldb命令,如图所示
35
36
这里就是方法列表。(这里没找到+ (void)say666这个方法,后续补上)
结语
以上就是个人对iOS类的探索和分析,还有一些不完整的地方,敬请批评指证,大家相互交流学习。