PS:在跟着看今年的WWDC2020
所有的session
,发现了很多更新,例如 objc_setHook_setAssociatedObject
的set
入口函数做了一层下沉,以及等等等等,最大的一个更新就是出现了 class_rw_ext_t
, 以前只有 ro rw,现在多了一个rwe。
写runtime
的代码,一点只用 API,不要写这些底层结构,但是一定要了解,因为你的API是随时可用的,如果你从 ro 里面读取 protocols
、properties
、methods
,那样一旦底层结构变了,代码就得更新了。
再次梳理了目前最新的 OC 类结构。以便同事们参考。
整体代码基于目前官方最近开源的OC底层源码版本objc4_781,主要研究 objc-runtime-new.h
文件,runtime 这部分包:
- OC 的声明后的实现部分开源。
- 底层调用的 C 和 C++ 开源。
- 汇编语言
.mm
,Apple 对于一部分CC++无法实现的功能使用,例如msgSend
整个逻辑关系图如下
1. objc_object 「万物之源」
连类的结构都是基于 objc_object
的,所以说 OC 里面一切皆对象呢。
objc_object
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
1.1 id 和 isa
id
的本质其实就是一个 objc_object *
的结构体指针
/// A pointer to an instance of a class.
typedef struct objc_object *id;
在结构体内部只有一个 Class
结构的 isa
isa
是什么?isa 的本质是 isa_t
,在另外一个文件 objc-private.h
中有 isa_t
的定义,是一个联合体,也叫共用体,可以参考这篇文章讲解共用体。
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
这个里面存储两个,Class cls
和 uintptr_t bits
。因为是联合体,所有实际只能有一个出现,ISA_BITFIELD是真正存储值的东西,它存储于文件 isa.h
,根据平台不同而不同,下面是 arm64
版本的:
ISA_BITFIELD
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD\
uintptr_t nonpointer : 1; /*0:代表普通指针,1:表示优化过的,可以存储更多信息。*/\
uintptr_t has_assoc : 1; /*是否设置过关联对象。如果没设置过,释放会更快*/\
uintptr_t has_cxx_dtor : 1; /*是否有C++的析构函数*/\
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; /*引用计数器是否过大无法存储在ISA中。如果为1,那么引用计数会存储在一个叫做SideTable的类的属性中*/\
uintptr_t extra_rc : 19; /*里面存储的值是引用计数器减1*/\
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
这个 ISA_MASK 这种的就是一个蒙版,isa 完整的值通过跟他进行与操作,就只留下了class的地址,(Class)(isa.bits & ISA_MASK)
话外一提,objc_object
的结构除了上面这个结构,根据这个结构存的值,提供了一整套的API方法,存储于 objc_private.h
文件中。
那么 Class 是什么呢?看2.0
2. class 结构体:objc_class
Class
是 objc_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() const {
return bits.data();
}
}
这里先后存储的东西分四个:
- 继承来的
isa
结构的指针,占8字节 - superClass 的 Class 指针,占8字节
- cache类型,本篇跳过。
- class_data_bits_t类型的bits,下段落展开。
3. 类中数据 class_data_bits_t
class_data_bits_t
里存储着当前类的很多信息,通过 class_data_bits_t 进行蒙版运算 (bits & FAST_DATA_MASK)
可以算出 class_rw_t
,简称就是 rw
。
下一段落重点讲解 class_rw_t
。
class_data_bits_t ==> class_rw_t ==> class_ro_t
class_data_bits_t ==> class_rw_t ==> class_rwe_t
这个关系是 2020 年新的变动,作为OC Swift的开发者一点要了解,具体看这段 WWDC Session
class_data_bits_t
struct class_data_bits_t {
friend objc_class;
// 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 操作
//使用 store-release fence 操作,类似一种轻量级的资源锁,
//如下面的`atomic_thread_fence `,因为可能会同时存在数据的读写
uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
atomic_thread_fence(memory_order_release);
bits = newBits;
}
//获取类的ro数据,即使在构建过程中。
//这个需要修复,至少在没有编译器屏障的情况下,并不是真正安全的。
//在 实现类 更改数据字段的时候,可能没有内存屏障的。
const class_ro_t *safe_ro() {
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;
}
}
}
上面代码提到了内存屏障,那么什么是内存屏障,这个概念涉及到代码的乱序执行,扩展里面单独说。这个注释的意思是说,imageLoader的时候,还在实现这个类的过程中,不要读取ro数据,因为还没有构建完成。
ro rw rwe 是相互依存的表述类结构的结构,ro 是在内存中直接读取出来的,只读readonly的,其中包括了我们熟悉的ivar,所以说ivar是不可以动态添加的,就是这个原因。
3.1 类数据演变出:class_ro_t
class_ro_t
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;
// This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
_objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
_objc_swiftMetadataInitializer swiftMetadataInitializer() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
return _swiftMetadataInitializer_NEVER_USE[0];
} else {
return nil;
}
}
method_list_t *baseMethods() const {
return baseMethodList;
}
class_ro_t *duplicate() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
class_ro_t *ro = (class_ro_t *)memdup(this, size);
ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
return ro;
} else {
size_t size = sizeof(*this);
class_ro_t *ro = (class_ro_t *)memdup(this, size);
return ro;
}
}
};
其中包含 const ivar_list_t * ivars
用来表示 ivar_list_t
和 ivar_t
。
3.1.1ivar_list_t
和 ivar_t
ivar_list_t
通过 entsize_list_tt
存储了 ivar_t
。
entsize_list_tt
是提供遍历器的通用容器,详细见我博客的另一篇文章,Apple源码用到的一些数据结构。
ivar_list_t
struct ivar_list_t : entsize_list_tt<ivar_t, ivar_list_t, 0> {
bool containsIvar(Ivar ivar) const {
return (ivar >= (Ivar)&*begin() && ivar < (Ivar)&*end());
}
};
ivar_t
struct ivar_t {
#if __x86_64__
// *offset was originally 64-bit on some x86_64 platforms.
// We read and write only 32 bits of it.
// Some metadata provides all 64 bits. This is harmless for unsigned
// little-endian values.
// Some code uses all 64 bits. class_addIvar() over-allocates the
// offset for their benefit.
#endif
int32_t *offset;
const char *name;
const char *type;
// alignment is sometimes -1; use alignment() instead
uint32_t alignment_raw;
uint32_t size;
uint32_t alignment() const {
if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
return 1 << alignment_raw;
}
};
3.2 类数据演变出:class_rw_t
rw 是可读可写的意思,runtime 动态增加方法等就是通过对 rw 进行修改写的操作完成的。
这里先看他的结构定义,对全局有了解。这里后续章节「。。。」详细展开更细致的研究。
class_rw_t
struct class_rw_t {
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
Class firstSubclass;
Class nextSiblingClass;
public:
class_rw_ext_t *deepCopy(const class_ro_t *ro);
const method_array_t methods();
const property_array_t properties();
const protocol_array_t protocols();
};
3.3 类数据演变出:class_rw_ext_t
三者的演变关系,我粘贴一段类构造过程的源码:如下:
// at file `objc-runtime-new.mm` static void methodizeClass(Class cls, Class previously)
auto rw = cls->data();
auto ro = rw->ro();
auto rwe = rw->ext();
但是 ro 其实才是一直不变的,也叫 clean memory,RW 是跟随runtime去操作变化的,叫做 Dirty Memory。
rwe 是什么其实很简单,2020年苹果出了iOS14,其SDK里面把rw中不常用的一部分砍了出去,变成了RWE,官方说,用 Mac 的 MailApp 做测试,Dirty Memory从80MB节约到了40MB。
class_rw_ext_t
struct class_rw_ext_t {
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
char *demangledName;
uint32_t version;
};
4. method_array_t、 method_list_t、 method_t
接下来三部分是:
- 类的
rw
rwe
中的method_array_t
- 类的
rw
rwe
中的property_array_t
- 类的
rw
rwe
中的protocol_array_t
这三个的继承数据结构都是:list_array_tt
,详情可见我博客的另一篇文章,Apple源码用到的一些数据结构。
通过 rw
rwe
中的 method_array_t 获取 methods,它的类型定义如下:
method_array_t
class method_array_t :
public list_array_tt<method_t, method_list_t>
{
typedef list_array_tt<method_t, method_list_t> Super;
public:
method_array_t() : Super() { }
method_array_t(method_list_t *l) : Super(l) { }
method_list_t * const *beginCategoryMethodLists() const {
return beginLists();
}
method_list_t * const *endCategoryMethodLists(Class cls) const;
method_array_t duplicate() {
return Super::duplicate<method_array_t>();
}
};
其中获取到的一个或多个 method_list_t
,具体原理移步到上面文章中。
method_list_t
// entsize 中的2个字符位用来做 fixup 标记。
struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> {
bool isUniqued() const;
bool isFixedUp() const;
void setFixedUp();
uint32_t indexOfMethod(const method_t *meth) const {
uint32_t I =
(uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize());
ASSERT(i < count);
return I;
}
};
通过遍历器遍历 method_t
method_t 就是表示一个方法或者函数(包含:函数名、返回值、参数、函数体)
method_t
struct method_t {
SEL name; // 方法名称
const char *types; // 编码(返回值类型、参数类型)
MethodListIMP imp; // 方法的地址/实现
struct SortBySELAddress :
public std::binary_function<const method_t&,
const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};
SortBySELAddress()
方法是方便函数进行排序,其实在 methodList中 函数是根据SEL name这个对象的内存地址进行排序的,后续寻找方法的时候,会用二分查找增加查找效率。
这里也是今年的一个变动,获取方法的 MethodListIMP 直接从sel里面内存平移8个字节就可以得到。
4.1 method_t 方法的数据结构
SEL是一个指向objc_selector 结构体的指针:
typedef struct objc_selector *SEL;
获取一个SEL的方法
SEL sel1 = @selector(selector);
SEL sel2 = sel_registerName("selector");
SEL sel3 = NSSelectorFromString(@"selector");
// SEL 转换成字符串
char *string1 = sel_getName(sel1);
NSString *string2 = NSStringFromSelector(sel1);
IMP 是指向方法实现提的函数指针,调用一个方法,本质上就是一个方法寻址的过程,通过SEL寻找IMP。
method_t,就是在两者之间做映射关系
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
4.2 Type Encodings
Type Encodings
就是一个编码,runtime 中对于 method_t 的返回值和参数,使用了Type Encodings 字符串来进行描述:
@encode()
指令可以将类型转换为 Type Encodings
字符串编码:
char *buf1 = @encode(int **);
char *buf2 = @encode(struct key);
char *buf3 = @encode(Rectangle);
OC 方法都有两个隐式参数,方法调用者 (id)self
和方法名 (SEL) _cmd
,所以我们才能在方法中使用 self
和 _cmd
;
如 -(void)test
,它的编码为 “v16@0:8”
,可以简写为 “v@:”
v
:代表返回值类型为 void
16
:代表所有参数所占的总字节数
@
:代表参数 1 类型为 id
0
:代表参数 1 从第几个字节开始存储
:
:代表参数 2 类型为 SEL
8
:代表参数 2 从第几个字节开始存储
具体这里在官网上有详细解释,See Type Encodings in Objective-C Runtime Programming Guide。
5. property_array_t、 property_list_t、 property_t
property_array_t
class property_array_t :
public list_array_tt<property_t, property_list_t>
{
typedef list_array_tt<property_t, property_list_t> Super;
public:
property_array_t() : Super() { }
property_array_t(property_list_t *l) : Super(l) { }
property_array_t duplicate() {
return Super::duplicate<property_array_t>();
}
};
property_list_t
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
property_t
struct property_t {
const char *name;
const char *attributes;
};
6. protocol_array_t、 protocol_list_t、 protocol_t
protocol_array_t
class protocol_array_t :
public list_array_tt<protocol_ref_t, protocol_list_t>
{
typedef list_array_tt<protocol_ref_t, protocol_list_t> Super;
public:
protocol_array_t() : Super() { }
protocol_array_t(protocol_list_t *l) : Super(l) { }
protocol_array_t duplicate() {
return Super::duplicate<protocol_array_t>();
}
};
protocol_list_t
struct protocol_list_t {
// count is pointer-sized by accident.
uintptr_t count;
protocol_ref_t list[0]; // variable-size
size_t byteSize() const {
return sizeof(*this) + count*sizeof(list[0]);
}
protocol_list_t *duplicate() const {
return (protocol_list_t *)memdup(this, this->byteSize());
}
typedef protocol_ref_t* iterator;
typedef const protocol_ref_t* const_iterator;
const_iterator begin() const {
return list;
}
iterator begin() {
return list;
}
const_iterator end() const {
return list + count;
}
iterator end() {
return list + count;
}
};
protocol_t
struct protocol_t : objc_object {
const char *mangledName;
struct protocol_list_t *protocols;
method_list_t *instanceMethods;
method_list_t *classMethods;
method_list_t *optionalInstanceMethods;
method_list_t *optionalClassMethods;
property_list_t *instanceProperties;
uint32_t size; // sizeof(protocol_t)
uint32_t flags;
// Fields below this point are not always present on disk.
const char **_extendedMethodTypes;
const char *_demangledName;
property_list_t *_classProperties;
const char *demangledName();
const char *nameForLogging() {
return demangledName();
}
bool isFixedUp() const;
void setFixedUp();
bool isCanonical() const;
void clearIsCanonical();
# define HAS_FIELD(f) (size >= offsetof(protocol_t, f) + sizeof(f))
bool hasExtendedMethodTypesField() const {
return HAS_FIELD(_extendedMethodTypes);
}
bool hasDemangledNameField() const {
return HAS_FIELD(_demangledName);
}
bool hasClassPropertiesField() const {
return HAS_FIELD(_classProperties);
}
# undef HAS_FIELD
const char **extendedMethodTypes() const {
return hasExtendedMethodTypesField() ? _extendedMethodTypes : nil;
}
property_list_t *classProperties() const {
return hasClassPropertiesField() ? _classProperties : nil;
}
};
6.isa 走位图
这个图解释网上太多了,没什么好说的,放这里,跳过不讲了。
拓展研究
内存屏障 memory barrier
上面源码中 class_ro_t *safe_ro()
的注释里提到了一个的 compiler barrier
,编译器屏障,这个是为了解决代码执行问题的。
指令重排是指:导致的代码乱序执行。举例来说:如果第一个语句涉及内存读取,第二个语句只是简单的指令计算,那么CPU为了提高效率,完全可能在第一个语句的内存读取完成之前,先执行完了第二个语句。
上下两条指令没有相关性,就可能发生。如果涉及一个值,就有可能发现。
另外一个概念叫做 as if serial
,重排序的原则是看上去像是顺序执行的,不影响单线程的最终一致性。
但是多线程是不保证一致性的。所以重排序是CPU的公共特性。
然后就出现了,内存屏障 memory barrier
屏障上下的指令不会发生重排序,在汇编层面,通过 lock 汇编指令完成,lock是锁总线,CPU访问内存的总线,等指令访问完了,其他CPU才能去访问。
lock 本身的前面的所有指令全部不能越过 lock 重排序。
拓展阅读:Linux内核同步机制之(三):memory barrier
GCD 里面的 barrier 栅栏函数,等待所有位于栅栏函数之前的操作执行完毕后执行
,跟这个命名类似,有相同也有不同,相同就是都是为了保证因为多线程和并发导致的前后指令/任务的乱序。
内存屏障是指令级别的,在CPU汇编指令级别上的。栅栏函数是GCD级别的,GCD又是iOS系统级别的机制。
References
- objc4_781 官方源码
- 简图记录-android fence机制
- Fence和非原子操作的ordering
- C语言共用体(C语言union用法)详解。
-
「注意这个已经过时了,新版本不是这样的」补充git上找到的一个 o_t c_t ro rw 的连通图,缺点是没有rwe,名字我自己瞎起的,我后面画图会参考这个。