笔者整理了一系列有关OC的底层文章,但愿能够帮助到你。这篇文章主要讲解的是方法查找原理分析。c++
2.iOS的OC对象的内存对齐性能优化
3.iOS的OC的isa的底层原理bash
5.iOS的OC的方法缓存的源码分析并发
iOS的开发中咱们会使用类中的各类方法,在OC中对方法的调用称为消息的发送
。对方法函数的使用每个iOS开发者都很熟悉的,可是方法函数是怎么在底层中是怎么查找的就是这篇文章主要来介绍的。app
为了方便介绍接下来的内容,建立一个macOS的项目,定义了一个TestObject
的类定义了一个testMethod
的方法,在main.m
的文件里面实现以下代码函数
#import <Foundation/Foundation.h>
#import "TestObject.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
TestObject *objc = [[TestObject alloc] init];
[objc testMethod];
}
return 0;
}
复制代码
而后在该项目的目录下用终端命令clang -rewrite-objc main.m
直接编译生成一个main.cpp
文件来查看上面代码的底层实现,最终获得代码以下oop
TestObject *objc = objc_msgSend(objc_getClass("TestObject"), sel_registerName("alloc")), sel_registerName("init"));
objc_msgSend(objc, sel_registerName("testMethod"));
复制代码
其中sel_registerName
函数至关于@selector
,在TestObject
类调用alloc
,init
和testMethod
等方法都是在底层经过objc_msgSend
来进行发送消息的,能够看出方法的本质就是经过objc_msgSend
来发送消息的。其中objc_msgSend
有两个参数,id
是消息的接收者,SEL
方法的编号。其中经过以前的objc4-756.2
的源码查找到能够知道,方法的调用在底层会分别被编译成objc_msgSend
, objc_msgSend_stret
, objc_msgSendSuper
和objc_msgSendSuper_stret
。若是调用父类的方法会编译成带有super
字段的函数,其中objc_msgSend_stret
是调用结构体的方法。源码分析
经过objc4-756.2
的源码找到objc_msgSend
的底层源码是经过汇编的方式来写的,接下来的源码介绍是在arm64
的架构下的。
//objc_msgSend函数的入口
ENTRY _objc_msgSend
//objc_msgSend没有窗口
UNWIND _objc_msgSend, NoFrame
//对比当前的p0第一位是否为空或者是taggedPointer,若是是nil会跑到LReturnZero中,
//若是是taggedPointer会跑到LNilOrTagged
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
//在正常的状况下不是nil不是taggedPointer,会执行到这里
//其中p13为isa,若是消息的接收者是对象经过isa能够找到类,若是是类能够找到元类
ldr p13, [x0] // p13 = isa
//这里就去到GetClassFromIsa_p16的宏方法,将p13为isa做为参数
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
复制代码
从中能够看到,进入到objc_msgSend
汇编里面会先判断传进来的接收者是否为空和是不是taggedpointer,若是都不是就先找到isa
,经过isa
找到class
,接下来介绍GetClassFromIsa_p16
。
.macro GetClassFromIsa_p16 /* src */
#if SUPPORT_INDEXED_ISA
// Indexed isa
mov p16, $0 // optimistically set dst = src
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa
// isa in p16 is indexed
adrp x10, _objc_indexed_classes@PAGE
add x10, x10, _objc_indexed_classes@PAGEOFF
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
#elif __LP64__
// 64-bit packed isa
and p16, $0, #ISA_MASK
#else
// 32-bit raw isa
mov p16, $0
#endif
.endmacro
复制代码
这部分的源码在arm64
架构下只会走#elif __LP64__
下的,经过传进来的isa
&ISA_MASK
获得class
,而且以p16返回,最终仍是返回上面的objc_msgSend
外面的,会继续执行LGetIsaDone
的CacheLookup
。
其中CacheLookup
有三种查找的方式CacheLookup NORMAL|GETIMP|LOOKUP
,NORMAL
是正常的流程,GETIMP
查找imp
,LOOKUP
方法的查找。
/********************************************************************
*
* CacheLookup NORMAL|GETIMP|LOOKUP
*
* Locate the implementation for a selector in a class method cache.
*
* Takes:
* x1 = selector
* x16 = class to be searched
*
* Kills:
* x9,x10,x11,x12, x17
*
* On exit: (found) calls or returns IMP
* with x16 = class, x17 = IMP
* (not found) jumps to LCacheMiss
*
********************************************************************/
#define NORMAL 0
#define GETIMP 1
#define LOOKUP 2
// CacheHit: x17 = cached IMP, x12 = address of cached IMP
#define CACHE (2 * __SIZEOF_POINTER__)
#define CLASS __SIZEOF_POINTER__
//这是在缓存cache_t中查找方法
.macro CacheLookup
//其中x16是找到的class,经过#CACHE获得16个字节,从而class右移16字节获得cache_t
//其中cache_t是一个结构体,占16字节,bucket_t占8个字节,mask和occupied分别占4个字节
//并将cache_t中的buckets赋值给p10,occupied和mask赋值给p11
// p1 = SEL, p16 = isa
ldp p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask
#if !__LP64__
and w11, w11, 0xffff // p11 = mask
#endif
//x12是获得的hash值
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
复制代码
这部分的内容就是查找到cache_t
,并在cache_t
查找传进来的方法是否在这里,具体的cache_t
的方法缓存能够看iOS的OC的方法缓存的源码分析这篇文章的介绍。在1
部分的内容判断buckect
的sel
与传进来的cmd
是否相等,便是否有缓存过的方法,若是缓存命中CacheHit
直接返回imp
,若是没有缓存的就去到2
部分的内容。执行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
复制代码
这部分是根据以前的传进来的$0
参数来判断须要执行那一部分。由上面的内容可知传进来的$0
为NORMAL
。接下来的执行__objc_msgSend_uncached
,至此objc_msgSend
经过cache_t
来快速查找部分就结束了,接下来的部分就是经过慢速的方法查找。
objc_msgSend
经过第2部分的cache_t
快速查找,在缓存中找不到有缓存的方法,此时就须要进行没有缓存的慢速查找。
这部分的内容就是
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
.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
复制代码
经过以前的文章iOS的OC源码分析之类的结构分析能够知道方法的是存类的bits
的ro
和rw
里面的methodList
的,在cache_t
里面找不到方法的时候,此时就须要在methodList
找了,而MethodTableLookup
就是为了这部份内容作的准备。最终会执行到__class_lookupMethodAndLoadCache3
这个函数。
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
复制代码
传进来的obj
是对象,sel
是方法的编号,cls
是类。而后直接调用lookUpImpOrForward
函数,此时进来的参数中initialize
是YES,cache
是NO,resolver
是YES,由于此时是在cache_t
缓存中找不到方法才执行到这里的。
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.lock();
checkIsKnownClass(cls);
if (!cls->isRealized()) {
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
// 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.assertLocked(); // Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists. { 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. imp = (IMP)_objc_msgForward_impcache; cache_fill(cls, sel, imp, inst); done: runtimeLock.unlock(); return imp; } 复制代码
这个lookUpImpOrForward
函数的代码有点多,就分开一点点地分析。其中runtimeLock
是防止线程并发竞争的锁。
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
复制代码
这里是再次判断若是有缓存的,直接在缓存中找到imp
返回出去。
runtimeLock.lock();
checkIsKnownClass(cls);
if (!cls->isRealized()) {
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
// 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 } 复制代码
上面这部分的内容是判断类是不是合法的,而且判断类是不是初始化了,若是没有初始化好的话,就须要进入到realizeClass
函数里面进行初始化,这个函数也是对当前的类的父类和元类都作了初始化,这部分的内容就是为了接下来的类在bits
里面查找方法作好准备的。
// Try this class's method lists. { Method meth = getMethodNoSuper_nolock(cls, sel); if (meth) { log_and_fill_cache(cls, meth->imp, sel, inst, cls); imp = meth->imp; goto done; } } static method_t * getMethodNoSuper_nolock(Class cls, SEL sel) { runtimeLock.assertLocked(); assert(cls->isRealized()); // fixme nil cls? // fixme nil sel? for (auto mlists = cls->data()->methods.beginLists(), end = cls->data()->methods.endLists(); mlists != end; ++mlists) { method_t *m = search_method_list(*mlists, sel); if (m) return m; } return nil; } static void log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer) { #if SUPPORT_MESSAGE_LOGGING if (objcMsgLogEnabled) { bool cacheIt = logMessageSend(implementer->isMetaClass(), cls->nameForLogging(), implementer->nameForLogging(), sel); if (!cacheIt) return; } #endif cache_fill (cls, sel, imp, receiver); } 复制代码
这部分的内容是在类class
里面查找方法。getMethodNoSuper_nolock
函数是在类cls
的data()里面的methodList
列表循环查找sel
。若是找到就返回method_t
。而且执行log_and_fill_cache
函数,到最后仍是会执行cache_fill
。此时会将方法再次缓存在cache_t
中。
// 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; } } } 复制代码
这部分的内容是在类里面查找不到方法了,须要去父类查找方法。由于咱们以前的查找都是对当前的类开启objc_msgSend
汇编查找和cls的bits的methodList查找的,父类的方法也是可能有缓存的,因此此时经过父类的循环首先是经过cache_getImp
函数来查找imp
。其中_objc_msgForward_impcache
是实际存储在其中的函数指针方法缓存。若是有直接执行log_and_fill_cache
函数直接done
,若是没有就break
出去。若是没有找到imp
或者找到imp
作转发了此时不缓存,会直接调用getMethodNoSuper_nolock
函数来查找。若是找到仍是会对这个方法作缓存的。
上面的介绍都是方法存在的,若是在方法查找的过程当中,查找不到的话是会报异常的,例如执行以下的代码
TestObject *testObject = [[TestObject alloc] init];
[testObject performSelector:@selector(testErrorMthod)];
复制代码
// 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. imp = (IMP)_objc_msgForward_impcache; cache_fill(cls, sel, imp, inst); 复制代码
最终会执行_objc_msgForward_impcache
函数,而_objc_msgForward_impcache
是汇编的
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
复制代码
由此可知,__objc_msgForward_impcache
会执行到__objc_msgForward
,最终会执行到__objc_forward_handler
。经过源码的查找,最后会执行objc_defaultForwardHandler
函数打印出错误的信息。
#if !__OBJC2__
// Default forward handler (nil) goes to forward:: dispatch.
void *_objc_forward_handler = nil;
void *_objc_forward_stret_handler = nil;
#else
// Default forward handler halts the process.
__attribute__((noreturn)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
#if SUPPORT_STRET
struct stret { int i[100]; };
__attribute__((noreturn)) struct stret
objc_defaultForwardStretHandler(id self, SEL sel)
{
objc_defaultForwardHandler(self, sel);
}
void *_objc_forward_stret_handler = (void*)objc_defaultForwardStretHandler;
#endif
复制代码
objc_msgSend
的底层是用汇编来写的呢?方法的查找是在objc_msgSend
的函数下进行的,这一个过程有快速查找和慢速查找。
objc_msgSend
快速查找,而objc_msgSend
是在汇编的状况下进行的。进入objc_msgSend
先判断第一位的内存值是否为空或者是taggedPointer
,若是是就走相应的流程。若是不是就是正常的流程就须要经过GetClassFromIsa_p16
找到isa
,经过isa
执行CacheLookup
去到类的cache_t
来查找是否缓存方法,若是没有就执行__objc_msgSend_uncached
。此时就至关于快速查找方法是找不到了,须要过分到慢速的查找。__objc_msgSend_uncached
能够执行MethodTableLookup
函数来为接下来须要在类的bits中查找的方法做准备。最终会在汇编中过渡到c++函数,执行class_lookupMethodAndLoadCache3
。经过lookUpImpOrForward
函数来分别遍历类和父类的方法列表中查找,若是找到就缓存在cache_t
中。若是没有找到,而且没有作消息转发
的操做,最终会执行_objc_msgForward_impcache
而后进去__objc_msgForward
的__objc_forward_handler
函数报错。至此,方法的查找底层原理就介绍完毕了。