写在前面: 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信息