1、Runtime的定义
将源代码转换为可执行的程序,通常要经过三个步骤:编译、链接、运行。
Objective-C 语言 是一门动态语言,在编译阶段并不知道变量的具体数据类型,也不知道所真正调用的哪个函数。只有在运行时间才检查变量的数据类型,同时在运行时才会根据函数名查找要调用的具体函数。这样在程序没运行的时候,我们并不知道调用一个方法具体会发生什么。
Objective-C 语言把一些决定性的工作从编译阶段、链接阶段推迟到运行时阶段,使得 Objective-C 变得更加灵活。这也就说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的代码。这就是 Objective-C Runtime 系统存在的意义。
Runtime 实际上是一个库,这个库使我们可以在程序运行时动态的创建对象、检查对象,修改类和对象的方法。
Runtime 基本是用 C 和汇编写的,可见苹果为了动态系统的高效而作出的努力。你可以在这里下到苹果维护的开源代码。
2、与Runtime进行交互
OC支持从三种不同的层级上与 Runtime 系统进行交互,分别是通过 Objective-C 源代码,NSObject类定义的方法,Runtime 函数的直接调用。
Objective-C 源代码
使用OC在编写代码的时候,基本都是使用类、属性、方法调用、协议、分类。但是在这些的背后,都是由runtime来支持的。我们平常所熟知的各种类型,背后都有runtime对应的C语言结构体,及C和汇编实现。比如类就被编译成objc_class
的结构体,方法被编译成method_t
的结构体等等。
NSObject 的方法
Cocoa 中大多数类都继承于 NSObject
类,也就自然继承了它的方法。作为大部分Objective-C类继承体系的根类的NSObject,其本身就具有了一些非常具有运行时动态特性的方法,使用这些方法也算是一种与Runtme的交互方式,举例如下:
//返回对象的类;
- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead");
//检查对象对应的类,与指定的类aClass及其父类进行判断。只要有相同的类,就判断为true
- (BOOL)isKindOfClass:(Class)aClass;
//检查对象所在的类,与指定的类aClass,如果一致就返回true
- (BOOL)isMemberOfClass:(Class)aClass;
//用来判断对象是否实现了指定协议类的方法;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
//用来判断对象能否响应指定的消息;
- (BOOL)respondsToSelector:(SEL)aSelector;
2、Runtime 的函数
Runtime 系统是一个由一系列函数和数据结构组成,具有公共接口的动态共享库。头文件存放于/usr/include/objc
目录下。许多函数允许你用纯C代码来重复实现 Objc 中同样的功能。在文件中引入Runtime的头文件#include <objc/runtime.h>
,就可以使用Runtime的方法,并且适合OC程序是一样的效果。举例如下:
Man * m = [[Man alloc] init];
//OC语句
Class mClass = [m class];
//runtime函数
Class objClass = object_getClass(m);
NSLog(@"mClass = %@, objClass = %@",mClass,objClass);
//打印结果如下
//2022-05-31 09:40:14.506089+0800 schemeUse[1860:59157] mClass = Man, objClass = Man
3、Runtime中类的数据结构
Objc 2.0之前的版本
Objc 2.0之后的版本
由OBJC 2.0后的版本的代码可以看出:
-
Class
类是一个objc_class *
指针的别名 -
objc_class
是继承objc_object
的结构体 - 由于
objc_object
有一个实例变量isa
,并且objc_object
是objc_class
的superClass,则在objc_class
必然是有isa
实例变量。 - 在Runtime系统中,对象被定义为一个
objc_object
类型的结构体,所以Class
也被看作是一类特殊的对象。 -
id
是一种objc_object *
的指针的别名
4、了解isa
从Objc 2.0以后的源代码可以看出,所有的类都是继承于objc_object
,而objc_object
中只有一个变量,即 isa_t
类型的isa
。也就是说Objective-C 对象都是 C 语言结构体,所有的对象都包含一个类型为 isa 的指针。所有与类相关的信息,都是在isa
中进行存储,比如说:是否指针优化、是否有关联对象、是否被弱引用等。
那么isa
的作用是什么呢?
-
方法查询机制。在Object-C中,对象的方法并不存储在各个对象的内存中(如果每一个对象都保存了自己能执行的方法,那么对内存的占用有极大的影响),而是保存在类的结构体中(class_ro_t)。当实例对象调用方法的时候,该对象要通过自己的
isa
找到对应的类,在类的方法列表(class_ro_t)中查找方法的实现。如果类的方法列表中不存在,就是用类的superClass
去父类的方法列表继续查找。
但是如果是类方法的调用,就会有不同。Objective-C为了在调用方法的时候,使得类方法和对象方法保持一致的方法查找机制,就引入了 元类的概念。每一个类的isa
都指向自己的元类,在类中定义的类方法,存储在元类中。在类方法的调用过程中,是根据isa查询到自己的元类,在元类的方法列表中,查找类方法实现。
isa指向
表明是否有关联对象(has_assoc)。
表明是否有弱应用(weakly_referenced)。
对象的引用计数信息(extra_rc)。
存储对象的类的地址信息(shiftcls)。
isa
的数据结构:
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if SUPPORT_PACKED_ISA
// extra_rc must be the MSB-most field (so it matches carry/overflow flags)
// nonpointer must be the LSB (fixme or get rid of it)
// shiftcls must occupy the same bits that a real class pointer would
// bits + RC_ONE is equivalent to extra_rc + 1
// RC_HALF is the high bit of extra_rc (i.e. half of its range)
// future expansion:
// uintptr_t fast_rr : 1; // no r/r overrides
// uintptr_t lock : 2; // lock for atomic property, @synch
// uintptr_t extraBytes : 1; // allocated with extra bytes
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
};
# else
# error unknown architecture for packed isa
# endif
// SUPPORT_PACKED_ISA
#endif
#if SUPPORT_INDEXED_ISA
# if __ARM_ARCH_7K__ >= 2
# define ISA_INDEX_IS_NPI 1
# define ISA_INDEX_MASK 0x0001FFFC
# define ISA_INDEX_SHIFT 2
# define ISA_INDEX_BITS 15
# define ISA_INDEX_COUNT (1 << ISA_INDEX_BITS)
# define ISA_INDEX_MAGIC_MASK 0x001E0001
# define ISA_INDEX_MAGIC_VALUE 0x001C0001
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t indexcls : 15;
uintptr_t magic : 4;
uintptr_t has_cxx_dtor : 1;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 7;
# define RC_ONE (1ULL<<25)
# define RC_HALF (1ULL<<6)
};
# else
# error unknown architecture for indexed isa
# endif
// SUPPORT_INDEXED_ISA
#endif
};
可以看出isa_t
主要是有Class cls
和不同系统下的结构体组成。在不同的系统下 ,结构体的参数是一样的,只是空间分配有所不同。这样的话。这里以"__arm64__“进行展示:
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
};
};
isa_t
是一个共用体union
。
union
:在一个“联合”内可以定义多种不同的数据类型, 一个该“联合”类型的变量中,允许装入该“联合”所定义的任何一种数据,这些数据共享同一段内存,以达到节省空间的目的。这种“union”只能存放一种数据,在isa_t
中cls和结构体只能使用一个变量。
示意图
nonpointer
自2013年苹果推出iphone5s之后,iOS的寻址空间扩大到了64位。我们可以用63位来表示一个数字(一位做符号位)。那么这个数字的范围是2^63 ,很明显我们一般不会用到这么大的数字,那么在我们定义一个数字时NSNumber *num = @100,实际上内存中浪费了很多的内存空间。
当然苹果肯定也认识到了这个问题,于是就引入了tagged pointer。tagged pointer是一种特殊的“指针”,其特殊在于,其实它存储的并不是地址,而是真实的数据和一些附加的信息。
苹果对于Tagged Pointer特点的介绍:
- Tagged Pointer专门用来存储小的对象,例如NSNumber, NSDate, NSString。
- Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free。
-
在内存读取上有着3倍的效率,创建时比以前快106倍。
如上面tagged pointer所说,如果isa指针仅表示类型的话,对内存显然也是一个极大的浪费。于是,就像tagged pointer一样,对于isa指针,苹果同样进行了优化。isa指针表示的内容变得更为丰富,除了表明对象属于哪个类之外,还附加了引用计数extra_rc,是否有被weak引用标志位weakly_referenced,是否有附加对象标志位has_assoc等信息。
isa_t其实可以看做有两个可能的取值,Class cls或struct。isa作为Class cls使用的时候,是我们一贯的认知,存储对应的地址。这样的话,内存是有比较大的浪费。所以绝大多数情况下,苹果采用了优化的isa策略,即,isa_t类型并不等同于Class cls, 而是struct。这在初始化isa的时候,表现很明显。
有关联对象标志位has_assoc
NSObject * obj = [[NSObject alloc] init];
NSLog(@"isa_t = %p", *(void **)(__bridge void*)obj);
//2022-06-07 09:50:26.535944+0800 schemeUse[2530:174823] isa_t = 0x21a1dfe7f289
objc_setAssociatedObject(obj, "xxxx", self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
NSLog(@"isa_t-associate = %p", *(void **)(__bridge void*)obj);
//2022-06-07 09:50:26.536051+0800 schemeUse[2530:174823] isa_t-associate = 0x21a1dfe7f28b
可以看出,添加关联对象以后,isa是有变化的,全部转成二进制数据进行对比:
对比上面“arm64”的示意图,可以比较出来,第二位数据变成了“1”,也就是
has_assoc
位变化了。
被弱引用标志位weakly_referenced
NSObject * obj = [[NSObject alloc] init];
NSLog(@"isa_t = %p", *(void **)(__bridge void*)obj);
//2022-06-07 10:13:45.789576+0800 schemeUse[2647:180952] isa_t = 0x21a1dfe7f289
__weak typeof(obj) weakObj = obj;
NSLog(@"isa_t-weak = %p", *(void **)(__bridge void*)obj);
//2022-06-07 10:13:45.789685+0800 schemeUse[2647:180952] isa_t-weak = 0x25a1dfe7f289
全部转成二进制数据进行对比,可以看出第42位weakly_referenced
位被置位1:
isa中的引用计数extra_rc
代码:
NSObject * obj = [[NSObject alloc] init];
NSLog(@"refCount = %ld", (long)CFGetRetainCount((__bridge CFTypeRef)(obj)));
NSLog(@"isa_t—1 = %p", *(void **)(__bridge void*)obj);
//2022-06-08 10:56:14.495684+0800 schemeUse[6396:483268] refCount = 1
//2022-06-08 10:56:14.495792+0800 schemeUse[6396:483268] isa_t-refrence-1 = 0x21a1dfe7f289
NSObject * obj_refrence1 = obj;
NSLog(@"refCount = %ld", (long)CFGetRetainCount((__bridge CFTypeRef)(obj)));
NSLog(@"isa_t-refrence-2 = %p", *(void **)(__bridge void*)obj);
//2022-06-08 10:56:14.495831+0800 schemeUse[6396:483268] refCount = 2
//2022-06-08 10:56:14.495864+0800 schemeUse[6396:483268] isa_t-refrence-2 = 0x41a1dfe7f289
NSObject * obj_refrence2 = obj;
NSLog(@"refCount = %ld", (long)CFGetRetainCount((__bridge CFTypeRef)(obj)));
NSLog(@"isa_t-refrence-3 = %p", *(void **)(__bridge void*)obj);
//2022-06-08 10:56:14.495897+0800 schemeUse[6396:483268] refCount = 3
//2022-06-08 10:56:14.495928+0800 schemeUse[6396:483268] isa_t-refrence-3 = 0x61a1dfe7f289
全部转成二进制数据进行对比,可以看出extra_rc
的19位数据的变化(需要指出的是,如果has_sidetable_rc
的标识位为1,那么也就意味着extra_rc
的19位二进制数据装不下对象的引用计数,也就保存到了sidetable一部分。这样的话引用计数就是两部分加起来。):
对象的实例化过程中对isa的创建过程
1、类调用alloc
方法时,runtime
系统内部会调用_objc_rootAlloc(self)
;
2、而 _objc_rootAlloc
方法中只是调用了callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
;
3、在callAlloc
方法中,分配对象的空间以及初始化isa
(initInstanceIsa(cls, dtor)
方法)。
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
//其他代码xxxxxxxx
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if (slowpath(!obj)) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
//其他代码zxxxxxxxxxx
}
4、initInstanceIsa
最终调用的是objc_object
类的inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
方法
inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
//nonpointer的值,在initInstanceIsa方法中默认传值为true。initIsa(cls, true, hasCxxDtor);
if (!nonpointer) {
//如果没没有使用isa优化,使用isa结构体中的cls,来接受对象对应的类信息。
isa.cls = cls;
} else {
//使用了isa优化,创建一个新的isa,处理相关的信息,然后赋值给对象的isa
//对象实例化的时候,需要处理的isa信息包括:has_cxx_dtor、shiftcls等
isa_t newisa(0);
/**其他代码**/
newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
isa = newisa;
}
}
对象的销毁过程中,isa的作用
1、对象销毁,会调用dealloc
方法,该方法内部调用的是_objc_rootDealloc(self);
2、_objc_rootDealloc
方法内部,调用起了obj->rootDealloc();
3、 rootDealloc
方法中,主要是对isa的信息进行判断,检查是否可以直接free对象
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer && //对象的isa是有优化的
!isa.weakly_referenced && //对象没有被弱引用
!isa.has_assoc && // 不存在关联对象
!isa.has_cxx_dtor && // 没有 有C++的析构函数
!isa.has_sidetable_rc)) //在sidetable中不存在引用计数
{
assert(!sidetable_present());
//直接free
free(this);
}
else {
//需要去处理isa中相关的信息
object_dispose((id)this);
}
}
4、在 object_dispose
方法中,调用objc_destructInstance(obj)
,清理isa中的相关信息,弱引用、关联对象、c++析构函数、引用计数等,清理完成以后,释放对象。
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor(); //获取isa中C++标志位的信息
bool assoc = obj->hasAssociatedObjects(); //获取isa中关联对象的信息
// This order is important.
if (cxx) object_cxxDestruct(obj); //C++标志位有数据,处理析构
if (assoc) _object_remove_assocations(obj); // 清理关联的对象
obj->clearDeallocating(); //清理弱引用、sidetable的引用计数
}
return obj;
}
//清理弱引用、sidetable的引用计数
inline void objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
5、类的结构
在runtime系统中,objc_class
是类的结构体,objc_class
继承于objc_object
。
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 //存储类的信息,成员变量,方法,协议等
//可以使用data通过一些与或操作,获取到成员变量、方法、协议等
class_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
//初始化,以及获取isa、类的信息的一些方法
///xxxxxxxx
};
可以看出,在objc_class
中,有三个数据:Class superclass
、cache_t cache
、class_data_bits_t bits
。
Class superclass
:表明当前类的父类,其本身也是一个Class
类型。
- 根元类的父类,是
NSObject
。 -
NSObject
的父类是nil。
cache_t cache
: cache的作用在于缓存方法, 方法调用的时候,可以更快速的找到方法的IMP
。
cache的数据结构为:
//方法缓存的结构,共占用16个字节
struct cache_t {
private:
//8个字节,存放方法的空间首地址信息(low 48位)和mask信息(high 16位),
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
//_maybeMask is the buckets mask
explicit_atomic<mask_t> _maybeMask; //4个字节,存储的时候是newCapacity - 1
#if __LP64__
uint16_t _flags;
#endif
//缓存的方法数量
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
//向缓存中添加方法
void insert(SEL sel, IMP imp, id receiver);
//取缓存方法列表
struct bucket_t *buckets() const;
//可以缓存方法的空间数量
unsigned capacity() const;
//已经缓存的方法数量
mask_t occupied() const;
//增加已缓存方法的数量
void incrementOccupied();
//设置_bucketsAndMayBeMask
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
//开辟缓存的空间
void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);
/**其他方法**/
};
cahce_t
是一个结构体,共计16个字节的大小。包含两个成员,一个是_bucketsAndMaybeMask
,一个是union
联合体,各占8个字节。cache_t
中的方法主要围绕这两个成员的操作进行的。
_bucketsAndMaybeMask
:实际上是一块内存的首地址。方法缓存主要是以buckets
(bucket_t
)进行存储,存储内容就是SEL
和IMP
。_bucketsAndMaybeMask
通过和 mask
的与操作,就能获得缓存的方法列表。
struct bucket_t *cache_t::buckets() const
{
uintptr_t addr = _bucketsAndMaybeMask.load(memory_order_relaxed);
return (bucket_t *)(addr & bucketsMask);
}
方法的缓存过程:
方法缓存调用的是void insert(SEL sel, IMP imp, id receiver);
方法。该方法要做的事情分为三块:
- 开辟缓存空间
- 缓存空间的扩容
- 方法插入缓存
void cache_t::insert(SEL sel, IMP imp, id receiver)
{
//1.开辟缓存空间
// Use the cache as-is if until we exceed our expected fill ratio.
mask_t newOccupied = occupied() + 1;
unsigned oldCapacity = capacity(), capacity = oldCapacity;
if (slowpath(isConstantEmptyCache())) {
// Cache is read-only. Replace it.
//如果缓存里面为空。先分配4个容量大小的空间。并调用reallocate去开辟空间
if (!capacity) capacity = INIT_CACHE_SIZE; // 1<<2
//开辟空间,并且给_bucketsAndMayBemask、_maybemask赋值
reallocate(oldCapacity, capacity, /* freeOld */false);
}
//2、缓存空间的扩容
else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
// Cache is less than 3/4 or 7/8 full. Use it as-is.
}
#if CACHE_ALLOW_FULL_UTILIZATION
else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
// Allow 100% cache utilization for small buckets. Use it as-is.
}
#endif
else {
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true);
}
//3、方法插入缓存,方法插入缓存的代码
bucket_t *b = buckets();
mask_t m = capacity - 1;//3
mask_t begin = cache_hash(sel, m);
mask_t i = begin;
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot.
do {
if (fastpath(b[i].sel() == 0)) {
incrementOccupied();// _occupied++;
b[i].set<Atomic, Encoded>(b, sel, imp, cls());
return;
}
if (b[i].sel() == sel) {
// The entry was added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
return;
}
} while (fastpath((i = cache_next(i, m)) != begin));
bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}
消息机制中,方法的缓存流程:
class_data_bits_t bits
: 保存类的成员变量、成员方法、协议、属性
在之前isa
的学习中,讨论过对象的方法不存储在各个对象的内存中,而是保存在类的结构体中。具体保存类结构的什么位置呢?就是class_data_bits_t bits
。使用该成员可以获取到一个类的成员变量、成员方法、协议、属性等信息。
class_data_bits_t
数据结构如下:
struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
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;
}
}
/*其他方法*/
};
这个结构体是一个64bit的成员变量bits,先来看看这64bit分别存放的什么信息:
可以看出数据的第4-48位,是存放一个指向class_rw_t结构体的指针,该结构体包含了该类的属性,方法,协议等信息,是
class_data_bits_t bits
的核心。下面就来说说核心部分:
class_rw_t
和class_ro_t
class_ro_t
说明:1、该属性是在编译期就生成的,它存储的内容包含了当前类的名称,以及method_list_t, protocol_list_t, ivar_list_t, property_list_t 这些类的基本信息,在class_ro_t里面是没有分类中定义的方法和协议的。
class_rw_t
说明:
1、是class_ro_t
外面的一层,在运行时生成的。在_objc_init方法中关于dyld的回调的map_images中,调用realizeClass
方法,该方法最终将分类的方法与协议都插入到自己的方法列表、协议列表中。
2、它不包含成员变量列表,因为成员变量列表是在编译期就确定好的,它只保存在class_ro_t中。
3、成员方法会保存在class_rw_t中。
4、class_rw_t中包含了一个指向class_ro_t的指针,可以方便的读取类的成员变量信息。
在调用realizeClass
方法之前,类的 class_data_bits_t
是指向class_ro_t
结构体;调用realizeClass
方法之后,class_data_bits_t
是指向class_rw_t
结构体,把分类中的方法汇总到class_rw_t
中。并且class_rw_t
会有一个变量指向class_ro_t
。
6、类的总结图示
类的大小:
类的结构总结图: