iOS底层探索四-类的原理
前言
阅读以下博文有助于更好的连贯理解本篇内容
一、 isa 分析到元类
1、元类
从对象原理三文篇中,对象isa & ISA_MASK得到shiftcls(类),如果按照分析对象流程继续分析shiftcls(类)会得到什么?
2、根元类
类的isa & ISA_MASK得到对象的元类,那么元类的isa & ISA_MASK会得到什么?可以无限套娃一直开辟内存吗?
元类isa & ISA_MASK得到一个新的内存地址:$3, $3指向NSObject,但是$3和 $4(根类:NSObject)的地址并不相同,而和 $5的元类的内存地址($6)相同。苹果称$6为根元类,$3为对象的根元类。
根元类:$3的isa & ISA_MASK得到的内存地址:$4和根元类:$3自身相同,说明类的内存地址并不能无限开辟。
3、isa 链路图
苹果官方链路图:
isa:一个指向该类定义的指针,该对象是该类定义的实例superclass:返回接收方父类的类对象 图中有两条链路图:
isa链路图:
-
Subclass:子类对象isa->子类类isa->子类元类isa->根元类isa->根元类isa自身 -
SuperClass:父类对象isa->父类类isa->父类元类isa->根元类isa->根元类isa自身 -
Rootclass:根类对象isa->根类类isa->根元类isa->根元类isa自身
superclass继承链:
-
类:子类->父类->根类->nil -
元类:子类元类->父类元类->根元类->根类->nil
苹果官方图不太容易理解,图解一下会更容易通途易懂
isa链路图:
superclass继承链:
二、类的结构
对象原理三文篇中知道 Class 类型是 objc_class 指针类型,objc_class 是一个结构体,因而Class的本质是结构体指针类型。Class 的结构体objc_class是如何定义的?
1、objc_class 定义
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// 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
......
};
结构体:objc_class包含四个成员变量:
Class ISA:隐藏参数 ISA,Class superclass:父类cache_t cache:以前是缓存指针和虚函数表class_data_bits_t bits:Class_rw_t 和 自定义rr/alloc标志 数据
ISA指针在对象原理三文篇中已经详细分析,superclass是类的父类,这个很好理解。cache_t cache 和 class_data_bits_t bits分别是什么呢?
2、 cache_t 定义
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
......
}
结构体 cache_t 包含两个成员变量:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask:指针类型,存放buckets的首地址- 联合体
union:联合体是互斥的,有我无他
_maybeMask:当前的缓存区count_flags:当前cache的可存储的buckets数量,默认是0_occupied:当前cache的可存储的buckets数量,默认是0_originalPreoptCache:初始时候的缓存(注意联合体互斥,如果有值,则同等级struct为空)
3、 class_data_bits_t 定义
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
private:
bool getBit(uintptr_t bit) const
{
return bits & bit;
}
// Atomically set the bits in `set` and clear the bits in `clear`.
// set and clear must not overlap.
void setAndClearBits(uintptr_t set, uintptr_t clear)
{
ASSERT((set & clear) == 0);
uintptr_t newBits, oldBits = LoadExclusive(&bits);
do {
newBits = (oldBits | set) & ~clear;
} while (slowpath(!StoreReleaseExclusive(&bits, &oldBits, newBits)));
}
void setBits(uintptr_t set) {
__c11_atomic_fetch_or((_Atomic(uintptr_t) *)&bits, set, __ATOMIC_RELAXED);
}
void clearBits(uintptr_t clear) {
__c11_atomic_fetch_and((_Atomic(uintptr_t) *)&bits, ~clear, __ATOMIC_RELAXED);
}
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
void setData(class_rw_t *newData)
{
ASSERT(!data() || (newData->flags & (RW_REALIZING | RW_FUTURE)));
// Set during realization or construction only. No locking needed.
// Use a store-release fence because there may be concurrent
// readers of data and data's contents.
uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
atomic_thread_fence(memory_order_release);
bits = newBits;
}
// Get the class's ro data, even in the presence of concurrent realization.
// fixme this isn't really safe without a compiler barrier at least
// and probably a memory barrier when realizeClass changes the data field
const class_ro_t *safe_ro() const {
class_rw_t *maybe_rw = data();
if (maybe_rw->flags & RW_REALIZED) {
// maybe_rw is rw
return maybe_rw->ro();
} else {
// maybe_rw is actually ro
return (class_ro_t *)maybe_rw;
}
}
......
}
结构体 class_data_bits_t包含两个成员变量:
class_rw_t* data():当前类在运行时添加的类中的属性、方法还有遵循的协议等信息const class_ro_t *safe_ro():当前类在编译期就已经确定的属性、方法以及遵循的协议等信息
4、class_ro_t 定义
struct class_ro_t{
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
union {
const uint8_t * ivarLayout;
Class nonMetaclass;
};
explicit_atomic<const char *> name;
// With ptrauth, this is signed if it points to a small list, but
// may be unsigned if it points to a big list.
void *baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
......
}
ro:read-only只读属性
5、class_rw_t 定义
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
class_ro_t_authed_ptr<const class_ro_t> ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
char *demangledName;
uint32_t version;
Class firstSubclass;
Class nextSiblingClass;
......
}
rw:read-write可读可写属性
6、class_ro_t 和 class_rw_t 区别
每个类创建都包含class_ro_t和class_rw_t两个结构体,不同的是class_ro_t存储在编译期就已经确定的属性、方法以及遵循的协议等信息,class_rw_t存储在运行时添加的类中的属性、方法还有遵循的协议等信息,确切的说是调用runtime的realizeClassapi时生成class_rw_t结构体,该结构体包含了class_ro_t,并更新data部分,指向class_rw_t结构体的地址。
realizeClass调用之前:
realizeClass调用之后:
三、类的取值
定义类Person:
1、属性列表
获取Person类属性:
2、实例方法列表
获取Person实例方法(也称对象方法):
获取类属性和实例方法发现,属性存放在属性列表preperty_list_t中,但是却没有类的成员变量,方法存放在方法列表method_list_t中,但是却没有类方法,那么成员变量和类方法存储到哪里了?WWDC2020有解释,成员变量存储在class_ro_t,并且在类调用在realizeClass方法后copyclass_ro_t到class_rw_t中,那需要获取
class_rw_t中的class_ro_t存储的数据。
3、成员变量列表
获取Person成员变量:
根据WWDC2020引导,确实从class_rw_t结构体包含的class_ro_t结构体中获取到类的成员变量,但是为什么类的成员变量存储在class_ro_t,类的属性存储在class_rw_t?成员变量和属性有什么区别呢?
成员变量:类的定义或者类的实现中,在{ }中所声明的变量都为成员变量。成员变量用于类内部(不会生成setter、getter方法),不需与外界接触的变量。实例变量:特殊的成员变量,实例变量本质上就是成员变量,只是实例是针对类而言。实例是指类的声明过程中,成员变量的类型是类类型,这类的成员变量就是实例变量。属性:是OC语言中的一个机制,在OC中用@property来声明一个属性,其实@property是一种语法糖,编译器会自动生成setter和getter方法。
定义Person类:
底层实现:(文篇对象原理中,有详细介绍如何查看底层源文件)
源文件中属性是带有_的成员变量,并且自动生成setter、getter 方法。
5、类方法列表
通过获取类的实例方法(对象方法)发现,对象方法存储在类中,并且类中只存储了对象方法。实例方法存储在类中,类方法会不会存储在元类中呢?
获取Person类方法:
获取元类方法列表,类方法存储在元类的class_rw_t结构体中,为了将实例方法和类方法重名问题区分开来。