Runtime简介
Runtime其实是一套由C语言API组合成的库,它会尽可能的把代码的决策过程推迟到运行时。
对于C语言,函数的调用在编译的时候会决定调用哪个函数。
对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。OC代码最终会转换成底层Runtime的代码,在运行时来根据函数名称找对应的函数。
它基本是用C和汇编写的,属于1个C语言库,包含了很多底层的C语言API,如跟类、成员变量、方法相关的API。
Runtime消息传递
当一个对象调用方法[obj msg]
,编译器会转换成这样objc_msgSend(obj, sel_registerName("msg"))
Student *s = [Student new];
//以下三种方式都能调用 run 方法
[s run];
objc_msgSend(s, sel_registerName("run"));
objc_msgSend(s, @selector(run));
调用方法流程
- 首先,通过 obj 的 isa 指针找到它的 class ;
- 在 class 的方法列表中找 run;
- 如果 class 中没到 run,则沿着继承链往上找;
- 一旦找到 run 这个函数,就去执行它的实现IMP
- 方法查找流程图
一些Runtime的术语
要想了解Runtime的机制,我们必须先了解Runtime的一些术语,他们都对应着数据结构
SEL(objc_selector)
它是selector在 Objc 中的表示(Swift 中是 Selector 类)。selector 是方法选择器,其实作用就和名字一样,日常生活中,我们通过人名辨别谁是谁,注意 Objc 在相同的类中不会有命名相同的两个方法。selector 对方法名进行包装,以便找到对应的方法实现。它的数据结构是
typedef struct objc_selector *SEL;
我们可以看出它是个映射到方法的 C 字符串,你可以通过 Objc 编译器命令@selector() 或者 Runtime 系统的 sel_registerName 函数来获取一个 SEL 类型的方法选择器。
id(objc_object)
是一个参数类型,它是指向某个类的实例的指针。定义如下:
struct objc_object { Class isa; };
typedef struct objc_object *id;
以上定义,看到objc_object 结构体包含一个 isa 指针,根据 isa 指针就可以找到对象所属的类。
Class(objc_class)
typedef struct objc_class *Class;
其实是指向objc_class结构体的指针。objc_class的数据结构如下:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
从objc_class可以看到,一个运行时类中关联了它的父类指针、类名、成员变量、方法、缓存以及附属的协议。
其中objc_ivar_list
和objc_method_list
分别是成员变量列表和方法列表:
// 单个成员
struct objc_ivar {
char * _Nullable ivar_name OBJC2_UNAVAILABLE;
char * _Nullable ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
// 成员变量列表
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
}
// 单个方法
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
// 方法列表
struct objc_method_list {
struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}
objc_ivar_list
结构体用来存储成员变量的列表,而objc_ivar
则是存储了单个成员变量的信息;同理,objc_method_list
结构体存储着方法数组的列表,而单个方法的信息则由objc_method
结构体存储。
下图实现是 super_class 指针,虚线时 isa 指针。而根元类的父类是 NSObject,isa指向了自己。而 NSObject 没有父类,所以指向nil。

Method(objc_method)
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
objc_method 存储了方法名,方法类型和方法实现:
- method_name:方法名
- method_types:方法类型
- method_imp:方法实现
Ivar(objc_ivar)
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char * _Nullable ivar_name OBJC2_UNAVAILABLE;
char * _Nullable ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
Ivar
是表示成员变量的类型,其中ivar_offset
是基地址偏移字节。
IMP
typedef id (*IMP)(id, SEL, ...);
它就是一个函数指针,这是由编译器生成的。当你发起一个ObjC
消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 IMP
这个函数指针就指向了这个方法的实现。
你会发现 IMP
指向的方法与 objc_msgSend
函数类型相同,参数都包含 id
和 SEL
类型。每个方法名都对应一个 SEL
类型的方法选择器,而每个实例对象中的 SEL
对应的方法实现肯定是唯一的,通过一组 id
和 SEL
参数就能确定唯一的方法实现地址。
而一个确定的方法也只有唯一的一组 id
和 SEL
参数。
Cache(objc_cache)
typedef struct objc_cache *Cache
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
Cache 为方法调用的性能进行优化,每当实例对象接收到一个消息时,它不会直接在 isa 指针指向的类的方法列表中遍历查找能够响应的方法,因为每次都要查找效率太低了,而是优先在 Cache 中查找。
Runtime查找方法的详细流程
汇编部分
向对象发送消息的时候系统会调用汇编的_objc_msgSend
方法
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
MESSENGER_START
/// x0 recevier
// 消息接收者 消息名称
cmp x0, #0 // nil check and tagged pointer check
b.le LNilOrTagged // (MSB tagged pointer looks negative)
ldr x13, [x0] // x13 = isa
and x16, x13, #ISA_MASK // x16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
LNilOrTagged:
b.eq LReturnZero // 检测是否为空// nil check
// tagged
mov x10, #0xf000000000000000
cmp x0, x10
b.hs LExtTag // 检测是否为taggedpointer(小数据)类型
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone //isa处理完毕
LExtTag:
// ext tagged
adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
ubfx x11, x0, #52, #8
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone //isa处理完毕
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
MESSENGER_END_NIL
ret // 如果是nil就返回
END_ENTRY _objc_msgSend
流程如下
- 首先拿到对象及sel,
cmp x0, #0
- 进入
LNilOrTagged
后先调用LReturnZero
检测对象及sel
是否为nil
,如果为nil
直接返回 - 然后进入
LExtTag
检测对象是否为小数据类型,如果是则进入到LGetIsaDone
调用CacheLookup
- 对象不是
nil
且不是taggedpointer
类型,则直接调用CacheLookup
去查找缓存是否存在IMP
CacheLookup
处理类型CacheHit
(缓存中找到IMP
,返回IMP
)CheckMiss
(缓存中没找到IMP
)add
(其他地方找到,用汇编把IMP
添加到缓存)
下面是CacheLookup方法的具体实现
.macro CacheHit
.if $0 == NORMAL
MESSENGER_END_FAST
br x17 // call imp
.elseif $0 == GETIMP
mov x0, x17 // return imp
ret
.elseif $0 == LOOKUP
ret // return imp via x17
.else
.abort oops
.endif
.endmacro
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz x9, LGetImpMiss
.elseif $0 == NORMAL
cbz x9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz x9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
.macro JumpMiss
.if $0 == GETIMP
b LGetImpMiss
.elseif $0 == NORMAL
b __objc_msgSend_uncached
.elseif $0 == LOOKUP
b __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
.macro CacheLookup
// x1 = SEL, x16 = isa
ldp x10, x11, [x16, #CACHE] // x10 = buckets, x11 = occupied|mask
and w12, w1, w11 // x12 = _cmd & mask
add x12, x10, x12, LSL #4 // x12 = buckets + ((_cmd & mask)<<4)
ldp x9, x17, [x12] // {x9, x17} = *bucket
1: cmp x9, x1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: x12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp x12, x10 // wrap if bucket == buckets
b.eq 3f
ldp x9, x17, [x12, #-16]! // {x9, x17} = *--bucket
b 1b // loop
3: // wrap: x12 = first bucket, w11 = mask
add x12, x12, w11, UXTW #4 // x12 = buckets+(mask<<4)
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp x9, x17, [x12] // {x9, x17} = *bucket
1: cmp x9, x1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: x12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp x12, x10 // wrap if bucket == buckets
b.eq 3f
ldp x9, x17, [x12, #-16]! // {x9, x17} = *--bucket
b 1b // loop
3: // double wrap
JumpMiss $0
.endmacro
- 结果
- 在缓存中找到
IMP
缓存,调用IMP
- 在缓存中没找到
IMP
缓存,调用objc_msgSend_uncached
进行查找
- 在缓存中找到
__objc_msgSend_uncached
方法中调用了MethodTableLookup
,即开始通过方法列表去寻找IMP
.endmacro
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band x16 is the class to search
MethodTableLookup
br x17
END_ENTRY __objc_msgSend_uncached
STATIC_ENTRY __objc_msgLookup_uncached
// MethodTableLookup
.macro MethodTableLookup
// push frame
stp fp, lr, [sp, #-16]!
mov fp, sp
// save parameter registers: x0..x8, q0..q7
sub sp, sp, #(10*8 + 8*16)
stp q0, q1, [sp, #(0*16)]
stp q2, q3, [sp, #(2*16)]
stp q4, q5, [sp, #(4*16)]
stp q6, q7, [sp, #(6*16)]
stp x0, x1, [sp, #(8*16+0*8)]
stp x2, x3, [sp, #(8*16+2*8)]
stp x4, x5, [sp, #(8*16+4*8)]
stp x6, x7, [sp, #(8*16+6*8)]
str x8, [sp, #(8*16+8*8)]
// receiver and selector already in x0 and x1
mov x2, x16
bl __class_lookupMethodAndLoadCache3
__class_lookupMethodAndLoadCache3
这个方法调用之后就开始C部分的查找,汇编部分到此结束
C语言部分
_class_lookupMethodAndLoadCache3
方法实现,调用了lookUpImpOrForward
// _class_lookupMethodAndLoadCache3
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
// lookUpImpOrForward
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if (cache) { // 判断是否有缓存
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.read();
// 判断这个类是否已经实现
if (!cls->isRealized()) {
// Drop the read-lock and acquire the write-lock.
// realizeClass() checks isRealized() again to prevent
// a race while the lock is down.
runtimeLock.unlockRead();
runtimeLock.write();
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
// 当前Class如果没有初始化,就进行初始化操作
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won`t happen. 2778172
}
retry:
runtimeLock.assertReading();
// Try this class`s cache.
// 尝试从该Class对象的缓存中查找,如果找到,就跳到done处返回该方法
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class`s method lists.
// 尝试从该Class对象的方法列表中查找,如果找到,就跳到done处返回该方法
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists.
// 尝试从该Class的父类方法列表和缓存中查找
{
unsigned attempts = unreasonableClassCount();
// 沿着继承链往上找
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
// 在当前superclass对象的缓存进行查找
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
// 如果在当前superclass的缓存里找到了方法,就调用log_and_fill_cache进行方法缓存
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don`t cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
// 在当前superclass对象的方法列表中进行查找
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
// 如果在当前superclass的方法列表中找到了方法,就调用log_and_fill_cache进行方法缓存
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
// 没有找到IMP,尝试进行方法解析
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don`t cache the result; we don`t hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver didn`t help.
// Use forwarding.
// 如果方法解析不成功,就进行消息转发
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
return imp;
}
动态方法解析
// No implementation found. Try method resolver once.
// 没有找到IMP,尝试进行方法解析
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don`t cache the result; we don`t hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
// 核心方法_class_resolveMethod
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
// 实例方法
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
// 类方法
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// 类方法动态解析调用完之后为什么还要调用实例方法的动态解析?
// 因为在元类中子类的类方法是以实例方法的姿态存在
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
// 实例方法解析
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
// 查看cls的meta-class对象的方法列表里面是否有SEL_resolveInstanceMethod函数,
// 也就是看是否实现了+(BOOL)resolveInstanceMethod:(SEL)sel方法
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
// 如果没找到,直接返回
return;
}
// 如果找到,则通过objc_msgSend调用一下+(BOOL)resolveInstanceMethod:(SEL)sel方法完成里面的动态增加方法的步骤
BOOL (*msg)(Class, SEL, SEL) = (__typeof__(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// Cache the result (good or bad) so the resolver doesn`t fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform(“RESOLVE: method %c[%s %s] ”
“dynamically resolved to %p”,
cls->isMetaClass() ? `+` : `-`,
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn`t add anything?
_objc_inform(“RESOLVE: +[%s resolveInstanceMethod:%s] returned YES”
“, but no new implementation of %c[%s %s] was found”,
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? `+` : `-`,
cls->nameForLogging(), sel_getName(sel));
}
}
}
// 类方法解析
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
// 断言
assert(cls->isMetaClass());
// 查看cls类的方法列表里面是否有SEL_resolveClassMethod函数,
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
// 如果没找到,直接返回
return;
}
// 如果找到,则通过objc_msgSend调用一下+(BOOL)resolveClassMethod:(SEL)sel 方法完成里面的动态增加方法的步骤
BOOL (*msg)(Class, SEL, SEL) = (__typeof__(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);
// Cache the result (good or bad) so the resolver doesn`t fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
// Cache the result (good or bad) so the resolver doesn`t fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform(“RESOLVE: method %c[%s %s] ”
“dynamically resolved to %p”,
cls->isMetaClass() ? `+` : `-`,
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn`t add anything?
_objc_inform(“RESOLVE: +[%s resolveClassMethod:%s] returned YES”
“, but no new implementation of %c[%s %s] was found”,
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? `+` : `-`,
cls->nameForLogging(), sel_getName(sel));
}
}
}
消息转发
动态方法解析完后还是没找到IMP,开始消息转发
// 如果方法解析不成功,就进行消息转发
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
消息转发这里直接就是返回了一个(IMP)_objc_msgForward_impcache
指针
STATIC_ENTRY __objc_msgForward_impcache
MESSENGER_START
nop
MESSENGER_END_SLOW
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr x17, [x17, __objc_forward_handler@PAGEOFF]
br x17
END_ENTRY __objc_msgForward