写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。
目录如下:
以上内容的总结专栏
细枝末节整理
前言
在之前的 iOS 底层原理探索 之 对象的本质 & isa的底层实现 文章中,我们探索了对象的本质,对象在内存中的存储结构,以及 isa
的底层实现。今天,我们就接着从头一个对象的 isa
的指向来看看类的底层原理结构是什么。
-
对象的
isa
首先我们在调试台,从一个对象的isa
开始:
入上图所示,我们先拿到p1
的内存结构,第一个地址存储的是它的isa
,与上掩码之后,我们拿到了它的类指针的值。
其实,一直都很好奇 这个类指针的内存结构到底是什么样子的,那么我们就接着查看下它的内存信息,发现,p1
的类也有自己的内存空间布局, 当我们拿着p1
的类的 isa
再一次与上 掩码的时候,po
下结果发现,也是p1
的类 SMPerson。
p1
的 类指针式 0x0000000100008360
p1
的 类指针的类指针式 0x0000000100008338
我们通过打印 SMPerson.class 的指针地址,可以确定 是 0x0000000100008360
,那么 0x0000000100008338
这个地址存储的是什么呢?
会不会是NSObject呢?
p/x NSObject.class
(Class) $7 = 0x000000010036a140 NSObject
打印地址后发现并不是。 那么,我们暂且说他是一个新的东西吧。
接着我们分析下
我们的对象有一个isa
指向了 它的类, 类也有一个 isa
指向了 元类
。
接着我们用烂苹果分析下边以后的可执行文件的内容:
可以看到除了有 _OBJC_CLASS_&_SMPerson
之外 还有一个 _OBJC_METACLASS_&_SMPerson
,然而,在编程的过程中我们并没有去写有关的代码,这说明系统或者编译器帮我生成好了一个额外的 MATEClass
。
既然发现了元类,那么,我们有必要再看看元类的isa
指向类哪里。
控制台分析如下, isa
的走位指向:
有些读者看的不是太直观,那么我们画个图更加直观一点:
-
isa
总结
- 对象的
isa
指向它的类; - 类的
isa
指向元类; - 元类的
isa
指向根元类; - 根元类的
isa
指向它自己。
总结了 isa
的指向, 我们知道,类是有继承关系的,那么,类的继承关系又是如何的一个走向,我们继续探索类的继承关系的走向。
void smTestNSObject(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);
// SMPerson元类
Class pMetaClass = object_getClass(SMPerson.class);
Class psuperClass = class_getSuperclass(pMetaClass);
NSLog(@"%@ - %p",psuperClass,psuperClass);
// SMTeacher -> SMPerson -> NSObject
// 元类也有一条继承链
Class tMetaClass = object_getClass(SMTeacher.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);
}
打印输出如下:
用一张图片来总结下调试台打印的信息就是:
-
类的继承关系总结
子类
继承自父类
父类
继承自NSObject
子元类
继承自它的父元类
父元类
继承自根元类
根元类
继承自NSObject类
NSObject类
类继承自nil
最后,整合下 类的继承关系
和 isa 指向的
关系,就是我们非常熟悉苹果给我们总结的的下图:
类的底层原理结构
准备
内存偏移
-
- 普通指针
int a = 10;
int b = 10;
NSLog(@"%d -- %p",a,&a);
NSLog(@"%d -- %p",b,&b);
10 -- 0x7ffeefbff3f4
10 -- 0x7ffeefbff3f8
-
- 对象指针
SMPerson *p1 = [SMPerson alloc];
SMPerson *p2 = [SMPerson alloc];
NSLog(@"%@ -- %p",p1,&p1);
NSLog(@"%@ -- %p",p2,&p2);
<SMPerson: 0x101442430> -- 0x7ffeefbff3d8
<SMPerson: 0x10144ccc0> -- 0x7ffeefbff3e0
-
- 数组指针
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);
0x7ffeefbff400 - 0x7ffeefbff400 - 0x7ffeefbff404
0x7ffeefbff400 - 0x7ffeefbff404 - 0x7ffeefbff408
我们可以发现,在数组中,数组的首地址存储的就是数组中的第零个元素的地址,接着是第一个元素的地址,元素之间的地址相差4字节,也就是元素(int)类型的大小。 如果我们将数组赋值给指针d,那么,d就是c的地址指针, d+1 就是按照现有的数据结构,向后平移一个地址。 也就是内存是可以平移,平移以后 取地址指针里面的值。
类的内存结构
我们都知道,对象在底层的内存结构中,在首地址存储的是类的isa
,接下来是对应的属性,成员变量的值。那么,对于类而言,在内存中的存储结构优势什么样子的呢?
类
我们通过对于objc
底层源码的分析可以知道,类在底层中的数据结构如下:
Class ISA
,Class superclass
,cache_t cache
,class_data_bits_t bits
探索到这里,我们整理下类的信息:
今天我们重点探索下 类中的 bits 的数据结构。
bits
今天我们主要探究下 这个 class_data_bits_t
类型的 bits
。
那么,根据我们对于内存平移的总结,我们只需要拿到类的首地址,以及知道 cache_t
所占用的内存大小,那么加上 isa
和 superclass
的 8 + 8 字节地址,就可以取到 bits
的内存中的值。
那么,我们看下这个cache_t
结构中都是包含什么。
cache_t 的底层实现如下(已将函数和static全局区的内容去掉):
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
};
可以看到 是有 uintptr_t 8字节 和 联合体中 8 字节 大小组成。也就是总共 16字节。
所以,我们在类的数据结构中,要通过内存平移的方式来获取到bits的值的话,需要平移 8 + 8 + 16 = 32字节的长度。
这样,我们就拿到了,类中 bits
的地址。
bits
的结构内容是:
struct class_data_bits_t {
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
}
class_rw_t
的结构如下:
整理下bits的结构
接下来我们就探索下,类的底层存储的信息,
properties
methods
在我们找到methods
之后,我们找到了method_array_t
,继续我们找到了method_t
结构体,内容如下:
这样,我们需要的方法的信息都在 big
这个结构体中, 那么,接着找下去,发现 big
方法会返回 我们想要的这个结构体的信息。所以在获取方法信息的时候,我们需要 使用 这个 big
。
在 属性 和 方法 数据中,我们并没有发现 类方法 和 成员变量并没有找到,那么这两部分存储到哪里了呢?
ivars
我们继续探索类的底层原理结构,发现在 结构体 class_ro_t
ro
中。
猜想
类方法
那么我们并没有发现类方法,因为对象方法都是存储在类中的,那么类方法是不是都存储在元类中呢?接下来我们验证下我们的猜想:
果然在元类的信息中,我们找到了类方法。
扩展
protocol
protocol也是存储在 bits
中的,所以我们来探究下 protocol。
首先,我们定义了一个 代理 来让 SMPerson 实现。
接着,我们在 class_rw_t
中找到了 protocols
,protocols
是一个 protocol_array_t
类型 ,通过 protocol_array_t
我们有找到了 protocol_ref_t
:
最终 protocol_ref_t
是一个 uintptr_t
。 接下来重点来了, 通过后面的注视 我猜想,是需要将 protocol_ref_t
强转成 protocol_t *
, 那么,我们接下来验证下这个想法:
然后我查看类的protocol信息