1. 前言
说起类,其实大家并不陌生,一个项目中,我们会创建很多类,在类里面也会定义很多成员变量、属性、方法等等,大家用的可以说是轻车熟路了。
不过我想大部分人在类在底层的实现并不是很了解,类在底层长什么样,我们定义的成员变量、属性,他们在底层储存在哪里呢?还有我们的方法、协议等等。
由于很多朋友已经写了大量的博客探索了这部分内容,本篇文章对于如何lldb进行探索调试不再讲解,而是写一些总结的内容,自我积累的同时也希望能帮到别人。
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:812157648,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!
2. 类的结构
首先来看一下下面这段代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
GYMPerson *person = [GYMPerson alloc];
Class pClass = object_getClass(person);
}
return 0;
}
这段代码中,创建了一个person对象,然后通过下面的方法获取其Class
OBJC_EXPORT Class _Nullable
object_getClass(id _Nullable obj)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
那么这个Class在底层是如何定义的呢?
通过查阅源码,可以得到:
typedef struct objc_class *Class;
原来Class的原型则是objc_class,继续查找可得:
struct 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
class_rw_t *data() {
return bits.data();
}
}
上面objc_class的代码省略了方法部分,我们主要看看其成员变量。这个类是个结构体,并且继承了objc_object,再继续看:
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
由上面可知,底层的Class主要有四个属性:isa、superclass、cache和bits。
-
isa:在之前的文章中已经讲解过,详见iOS底层isa探索分析。
-
superclass:指针类型,记录了当前类的父类。
-
cache:主要做了一些方法的缓存,方便方法的快速调用,这里不做详细讲解。
-
bits:本篇文章重中之重,这里存储了很多信息,详见下面。
3. 类的属性方法等存储
由类的结构可知,在bits里面可能存储了一些相关信息,那么针对这个bits,我们在具体看一下。
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
bits的类中定义了一个很重要的方法,如上面,这里面返回了一个class_rw_t类型的指针。class_rw_t类结构如下(方法部分省略):
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
}
在这里面,我们看到methods properties protocols这些数组变量,难道对应的方法、属性以及协议都存储在这里了吗? 答案是存储到这里了,但并不是最原始的,因为开发过程中我们可以通过运行时动态的添加属性和方法等,而后加的这些确实会存储在class_rw_t里面。
在class_rw_t里面还有一个ro指针变量,看一下它的结构:
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
在这个class_ro_t结构中,重点看一下ivars baseMethodList baseProtocols baseProperties.
baseProperties中存储了类中最原始的属性。
baseMethodList中存储了类中最原始的方法。
baseProtocols中存储了类中最原始的协议。
ivars则存储了类中的成员变量,包括由属性转过来带下划线的成员变量。
至于说类的类方法,类方法不会存储在本类中,而是存储在该类的元类中,元类和类的结构是一样的,探索方法也是一样的。
4. 总结
类在底层是一种struct结构,主要有个四个成员变量:
isa
superclass
cache
bits
类中所有的原始成员变量,属性,方法以及协议都存储在class_ro_t结构(俗称ro)中。同时会将ro中的成员变量,属性,方法以及协议赋值一份到class_rw_t(俗称rw)中,我们平时在运行时类中属性方法等的改动,也只是在修改rw中对应的属性方法等,不会影响到ro中的数据的。
还有类的类方法是存储在该类的元类中哦!
原文作者:Daniel_Coder