Objective-C 是一个动态语言,这意味着它不只须要一个编译器,也须要一个运行时系统来动态得建立类和对象、进行消息传递和转发。理解 Objective-C 的 Runtime 机制能够帮咱们更好的了解这个语言,适当的时候还能对语言进行扩展,从系统层面解决项目中的一些设计或技术问题。了解 Runtime ,要先了解它的核心 - 消息传递 (Messaging)。c++
Runtime
基本是用 C 和汇编写的,可见苹果为了动态系统的高效而做出的努力。你能够在这里 密码:tuw8 下到苹果维护的开源代码。苹果和 GNU 各自维护一个开源的 runtime 版本,这两个版本之间都在努力的保持一致。缓存
一个对象的方法像这样[obj foo],经过 clang -rewrite-objc
命令查看编译后的代码(因为以前的文章操做过,这里不详细解释操做流程了),编译器转成消息发送objc_msgSend(obj, foo)bash
objc_msgsend
底层有两种查找方式:架构
为何要使用汇编?函数
先在刚刚提供的objc源码里查找objc_msgSend
,找到咱们经常使用的架构arm64
汇编文件,能够看到ENTRY _objc_msgSend
,其实就是这个函数的入口 oop
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
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
// tagged
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]
adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
cmp x10, x16
b.ne LGetIsaDone
// 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
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY _objc_msgSend
复制代码
可能看不懂汇编,根据注释大概推测其意思,下面的代码主要作了非空检查和无标记指针检查(若是指针小于等于 LNilOrTagged 直接return返回)post
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
复制代码
接下来看以下代码,根据isa获取这个类,LGetIsaDone
是表示isa处理完毕,CacheLookup NORMAL
表示直接调用当前的imp
或者发送objc_msgSend_uncached
无缓存消息ui
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
复制代码
在当前文件里搜索CacheLookup
,来到它的宏定义this
.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
复制代码
在CacheLookup宏定义这里spa
CacheHit
: 缓存命中,方法的实现IMP在寄存器中,而后传递出去CheckMiss
: 缓存没命中,发送_objc_msgSend_uncachedadd
: 若是缓存里没找到,去其余地方查找到该方法实现后添加到缓存
.macro CacheHit
.if $0 == NORMAL
TailCallCachedImp x17, x12 // authenticate and call imp
.elseif $0 == GETIMP
mov p0, p17
AuthAndResignAsIMP x0, x12 // authenticate imp and re-sign as IMP
ret // return IMP
.elseif $0 == LOOKUP
AuthAndResignAsIMP x17, x12 // authenticate imp and re-sign as IMP
ret // return imp via x17
.else
.abort oops
.endif
.endmacro
复制代码
看到CacheHit
的宏定义: 在上面调用的时候,传递过来的是NORMAL
,执行了TailCallCachedImp
,即若是缓存命中的话,则返回缓存里的IMP
.
.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
复制代码
再看到CheckMiss
的宏定义:在上面调用的是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
// 省略
// 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
//省略
.endmacro
复制代码
前面后面的汇编代码只能看到有作字节对齐的操做,不过因为不懂汇编,具体作什么不是很清楚,不过看到了跳转进了一个很是重要的函数__class_lookupMethodAndLoadCache3
继续搜索__class_lookupMethodAndLoadCache3
发现并不能在当前汇编文件里找到声明,这时猜测会不会是跳转到了代码里
因而全局搜索class_lookupMethodAndLoadCache3
,果真在objc-runtime-new.mm
文件里找到了它的函数实现
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
复制代码
因此这里经过调用lookUpImpOrForward
开启了慢速查找
的过程
来到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.lock();
checkIsKnownClass(cls);
if (!cls->isRealized()) {
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
}
//先省略
}
复制代码
initialize
为YES,cache
为NO,resolver
为YESLGetIsaDone
这个判断是在isa处理完毕后才走缓存查找的汇编代码的,因此这个类是加载解析好的,即initialize
和resolver
都为YES,cache
为NO是由于在汇编里快速查找没有找到方法缓存才会执行到这里,因此这里确定为NOcache_getImp
去获取imp,因为传递进来的cache为NO,因此这里不会执行// Optimistic cache lookup
// 查找缓存!!!
if (cache) {
//汇编代码的方式实现的!
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
//汇编代码(在以前上面的汇编文件里)
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
CacheLookup GETIMP
复制代码
checkIsKnownClass(cls);
检查这个类是否已知,若是未知则抛出异常realizeClass(cls)
和初始化方法_class_initialize (_class_getNonMetaClass(cls, inst));
在lookUpImpOrForward
里还有retry相关的代码,继续分析
retry:
runtimeLock.assertLocked();
// Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{
//Method(SEL IMP)
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.
{
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.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
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.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
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;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
//_objc_msgForward
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
复制代码
第一步看到这里再一次调用了cache_getImp(cls, sel);
去从缓存中获取imp,传递进来明明已经知道是NO了,为何再去查找一次呢?
在objc_init
的时候有一个函数remap(cls)
,在汇编最开始查找该方法的时候若是没有方法缓存,但可能会在这个类初始化方法objc_init
的过程当中,对这个类进行了重映射remap
,即把该方法添加到方法缓存里了,因此这里要再去查找一次cache
有缓存就可能会节省不少时间
接下来先从当前类的方法列表method_list
去找,找到了就log_and_fill_cache
打印日志并把方法添加到缓存中
若是没找到则继续找父类的缓存cache_getImp(curClass, sel)
,再找父类的方法列表,和以前在本类的查找顺序同样,找到了也是添加到方法缓存log_and_fill_cache
动态方法解析和消息转发
,篇幅缘由,接下来的这个过程在Runtime底层原理(二)动态方法解析和消息转发以上均为我的探索源码的理解和所得,若有错误请指正,欢迎讨论。