一套以c、c++以及彙編寫成的,能夠爲Object-C提供運行時功能的api。源碼參考c++
Runtime其實有兩個版本: 「modern」 和 「legacy」。咱們如今用的 Objective-C 2.0 採用的是現行 (Modern) 版的 Runtime 系統,只能運行在 iOS 和 macOS 10.5 以後的 64 位程序中。而 maxOS 較老的32位程序仍採用 Objective-C 1 中的(早期)Legacy 版本的 Runtime 系統。api
這兩個版本最大的區別在於當你更改一個類的實例變量的佈局時,在早期版本中你須要從新編譯它的子類,而現行版就不須要。緩存
熟悉Object-C的同窗都知道,對象的方法調用在底層實際上是一個消息發送的過程,接下來咱們驗證一下。多線程
首先定義一個對象Son,它包含一個實例方法。而後咱們在main.m中調用,再經過clang -rewrite-objc main.m,生成一個.cpp文件,對好比下:併發
--------------main.m---------------
Son *son = [Son new];
[son son_instanceSelector];
--------------main.cpp---------------
Son *son = ((Son *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Son"), sel_registerName("new"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)son, sel_registerName("son_instanceSelector"));
複製代碼
能夠分析得出,對象的方法調用在底層都被編譯成了objc_msgSend(id _Nullable self, SEL _Nonnull op, …)app
再來看他的父類方法調用,聲明兩個類,Father、Son(繼承Father),而後在Son的實例方法與類方法中分別實現父類對應的實例方法與類方法,而後在經過clang編譯,比較:函數
-----------------------------Son.m-----------------------------
-(void)son_instanceSelector{
[super father_instanceSelector];
}
+(void)son_classSelector{
[super father_classSelector];
}
-----------------------------Son.cpp---------------------------
static void _I_Son_son_instanceSelector(Son * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Son"))}, sel_registerName("father_instanceSelector"));
}
static void _C_Son_son_classSelector(Class self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)(&(__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getMetaClass("Son"))}, sel_registerName("father_classSelector"));
}
複製代碼
能夠得出,super的方法調用在底層會轉化爲objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...),就是去父類的方法列表中查找方法,而後調用。oop
注意:方法調用的主體仍是子類對象。佈局
因此,咱們平時的方法調用使用Runtime底層函數來實現是什麼樣子呢?以下:post
// 類對象實例方法調用
objc_msgSend(son, sel_registerName("son_instanceSelector"));
// 類方法調用
objc_msgSend(objc_getClass("Son"), sel_registerName("son_classSelector"));
// 向父類發消息(實例方法)
struct objc_super kmSuper;
kmSuper.receiver = son;
kmSuper.super_class = [Father class];
objc_msgSendSuper(&kmSuper, @selector(father_instanceSelector));
//向父類發消息(類方法)
struct objc_super myClassSuper;
myClassSuper.receiver = [son class];
myClassSuper.super_class = class_getSuperclass(objc_getMetaClass("Son"));
objc_msgSendSuper(&myClassSuper, NSSelectorFromString(@"father_classSelector"));
複製代碼
objc_msgSend的快速查找流程是用匯編實現的,主要緣由有
objc_msgSend的彙編源碼:
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
// person - isa - 類
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
複製代碼
接着查看CacheLookup函數源碼:
.macro CacheLookup
// p1 = SEL, p16 = isa
ldp p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask
#if !__LP64__
and w11, w11, 0xffff // p11 = mask
#endif
and w12, w1, w11 // x12 = _cmd & mask
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
add p12, p12, w11, UXTW #(1+PTRSHIFT) // p12 = buckets + (mask << 1+PTRSHIFT)
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // double wrap
JumpMiss $0
.endmacro
複製代碼
一、經過isa偏移16位,拿到類的方法緩存中的buckets、以及occupied和mask(類的方法緩存)
二、查看是否緩存命中,有則retun imp
經過彙編查找方法緩存,緩存命中,就是方法查找的快速流程,未命中則開始走方法查找的慢速流程。
三、緩存未命中,調用CheckMiss函數
CheckMiss源碼:
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz p9, LGetImpMiss
.elseif $0 == NORMAL
cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
複製代碼
由於咱們以前調用的CacheLookup NORMAL,因此會走到objc_msgSend_uncached:
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p16 is the class to search
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
複製代碼
裏面僅有一段函數調用-MethodTableLookup:
.macro MethodTableLookup
// push frame
SignLR
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
// IMP in x0
mov x17, x0
// restore registers and return
ldp q0, q1, [sp, #(0*16)]
ldp q2, q3, [sp, #(2*16)]
ldp q4, q5, [sp, #(4*16)]
ldp q6, q7, [sp, #(6*16)]
ldp x0, x1, [sp, #(8*16+0*8)]
ldp x2, x3, [sp, #(8*16+2*8)]
ldp x4, x5, [sp, #(8*16+4*8)]
ldp x6, x7, [sp, #(8*16+6*8)]
ldr x8, [sp, #(8*16+8*8)]
mov sp, fp
ldp fp, lr, [sp], #16
AuthenticateLR
.endmacro
複製代碼
作了一些內存上的準備工做,而後調用函數**_class_lookupMethodAndLoadCache3:**
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
複製代碼
從這裏開始,便從彙編進入到了C/C++。也就是真正的方法慢速查找流程。
在快速查找流程中,方法緩存未命中。也就是說,快速查找行不通的時候,底層就會走到慢速查找流程,並一路從彙編走到lookUpImpOrForward函數。
lookUpImpOrForward源碼:
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) {
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// 判斷是否須要從緩存查找,是則先去方法緩存查找
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// 加鎖,防止多線程併發引發的返回錯誤
runtimeLock.lock();
checkIsKnownClass(cls);
// 方法查找以前的準備工做
// 類、元類、以及父類、父類的元類、直到它的根類、根元類
if (!cls->isRealized()) {
realizeClass(cls);
}
// 確保類已經初始化完成
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
}
retry:
runtimeLock.assertLocked();
// 先去類的方法緩存中查找一次,多線程併發調用時可能已經存在以前的調用緩存
imp = cache_getImp(cls, sel);
if (imp) goto done;
// 在當前的類方法列表中查找
{
Method meth = getMethodNoSuper_nolock(cls, sel);
// 若是找到方法,先緩存,而後goto done(return imp)
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// 查找父類的方法緩存和方法列表
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// 遞歸報錯
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// 先去父類的方法緩存中查找
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// 在父類緩存中找到方法,將方法緩存在子類中
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
break;
}
}
// 在父類的方法列表中查找
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
// 在父類的方法列表中找到方法,將方法緩存在子類中
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// 沒有找到方法實現,調用一次方法動態解析
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
// 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;
}
// 未能找到方法實現,且方法的動態解析也沒有用的時候
// 就會走到消息轉發流程(下篇文章會講)
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
}
複製代碼
方法的慢速查找遵循着一個規律,即先找類自己的方法,找不到則找父類方法,一直找到NSObject。
_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);
}
}
}
複製代碼
若是咱們在類中實現了resolveInstanceMethod或者resolveClassMethod方法而且正確處理了sel,能夠避免程序報錯。他會返回一個方法實現imp,並讓程序去再次查找。 例:
咱們在主線程中調用person對象一個並未實現的實例方法
Person *per = [Person alloc];
[per performSelector:@selector(run)];
複製代碼
而後,在Person.m中加入動態解析函數:
----------------------Person.m----------------------
#import "Person.h"
#import <objc/runtime.h>
@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(run)) {
class_addMethod(self, sel, (IMP)methodImp, "v@:");
return YES;
}
return NO;
}
void methodImp(id self,SEL _cmd){
NSLog(@"來了老弟...");
}
@end
複製代碼
打印結果:
來了老弟...
resolveClassMethod方法同理。
方法查找的流程圖以下:
(消息轉發流程下篇分析)