前言
阅读以下博文有助于更好的连贯理解本篇内容
一、 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的realizeClass
api时生成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
结构体中,为了将实例方法
和类方法
重名问题区分开来。