欢迎阅读iOS探索系列(按序阅读食用效果更加)html
书接上文说到cache_t
缓存的是方法,那么方法又是什么呢?c++
Runtime
是一套API
,由c、c++、汇编
一块儿写成的,为OC
提供了运行时git
Runtime有两个版本——Legacy
和Modern
,苹果开发者文档都写得清清楚楚github
源码中-old
、__OBJC__
表明Legacy
版本,-new
、__OBJC2__
表明Modern
版本,以此作兼容算法
Runtime
底层通过编译会提供一套API和供FrameWork
、Service
使用 缓存
Runtime
调用方式:bash
经过clang编译成cpp文件
能够看到底层代码,获得方法的本质多线程
FXPerson *p = [FXPerson alloc];
[p fly];
复制代码
FXPerson *p = ((FXPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("FXPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("fly"));
复制代码
((FXPerson *(*)(id, SEL))(void *)
是类型强转(id)objc_getClass("FXPerson")
获取FXPerson类对象sel_registerName("alloc")
等同于@selector()
那么能够理解为((类型强转)objc_msgSend)(对象, 方法调用)
并发
方法的本质是经过objc_msgSend
发送消息,id
是消息接收者,SEL
是方法编号app
若是外部定义了C函数并调用如
void fly() {}
,在clang编译以后仍是fly()
而不是经过objc_msgSend
去调用。由于发送消息就是找函数实现的过程,而C函数能够经过函数名
——指针
就能够找到
#import <Foundation/Foundation.h>
#import <objc/message.h>
@interface FXFather: NSObject
- (void)walk;
+ (void)run;
@end
@implementation FXFather
- (void)walk { NSLog(@"%s",__func__); }
+ (void)run { NSLog(@"%s",__func__); }
@end
@interface FXSon: FXFather
- (void)jump;
+ (void)swim;
@end
复制代码
子类FXSon
有实例方法jump
、类方法swim
父类FXFather
有实例方法walk
、类方法run
①发送实例方法
消息接收者——实例对象
FXSon *s = [FXSon new];
objc_msgSend(s, sel_registerName("jump"));
复制代码
②发送类方法
消息接收者——类对象
objc_msgSend(objc_getClass("FXSon"), sel_registerName("swim"));
复制代码
objc_msgSend
不能向父类发送消息,须要使用objc_msgSendSuper
,并给objc_super
结构体赋值(在objc2中只须要赋值receiver
、super_class)
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};
#endif
复制代码
③向父类发送实例方法
receiver——实例对象
;super_class——父类类对象
struct objc_super superInstanceMethod;
superInstanceMethod.receiver = s;
superInstanceMethod.super_class = objc_getClass("FXFather");
objc_msgSendSuper(&superInstanceMethod, sel_registerName("walk"));
复制代码
④向父类发送类方法
receiver——类对象
;super_class——父类元类对象
struct objc_super superClassMethod;
superClassMethod.receiver = [s class];
superClassMethod.super_class = class_getSuperclass(object_getClass([s class]));
objc_msgSendSuper(&superClassMethod, sel_registerName("run"));
复制代码
若是出现Too many arguments to function call, expected 0, have 2
问题,来到BuildSetting
把配置修改为以下图
objc_msgSend
是用汇编写成的,至于为何不用C而是用汇编写,是由于:
打开objc
源码,因为主要研究arm64结构
的汇编实现,来到objc-msg-arm64.s
①开始objc_msgSend
②判断消息接收者
是否为空,为空直接返回
③判断tagged_pointers
(以后会讲到)
④取得对象中的isa
存一份到p13
中(寄存器指令在逆向篇中会讲到)
⑤根据isa
进行mask
地址偏移获得对应的上级对象
(类、元类)
GetClassFromIsa_p16
定义,主要就是进行
isa & mask
获得
class
操做
(其定义方式与iOS探索 isa初始化&指向分析一文中提到的shiftcls
殊途同归)
⑥开始在缓存中查找imp
——开始了快速流程
从CacheLookup
开始了快速查找流程(此时x0是sel
,x16是class
)
#CACHE
是个宏定义表示16个字节,
[x16, #CACHE]
表示
类对象
内存地址偏移
16字节
获得
cache
。
cache
一分为二——8字节的
buckets
存放在p10,两个4字节的
occupied
和
mask
存放在p11
#define CLASS __SIZEOF_POINTER__
#define CACHE (2 * __SIZEOF_POINTER__)
复制代码
②x1是sel即cmd
,取出p11中的低32位(w11)——mask
,二者进行与运算获得hash下标
存放在x12
③p12先左移动(1+PTRSHIFT)
,再与p10buckets
相加获得新的p12——bucket
④拿出p12bucket
地址所在的值,放在p17imp
和p9sel
中,这点能够从bucket_t
的结构中看出(sel强转成key)用bucket
中的sel
与x1cmd
做对比,若是相同则缓存命中CacheHit
获得其中的imp
;若是不等就跳转⑤
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
MethodCacheIMP _imp;
cache_key_t _key;
#else
cache_key_t _key;
MethodCacheIMP _imp;
#endif
...
}
复制代码
⑤若是bucket->sel == 0
则CheckMiss
;比较p12bucket
和p10buckets
,若是不相等就将x12bucket
的值进行自减操做(查找上一个bucket
),跳转回④从新循环,直到bucket == buckets
遍历结束跳转⑥
.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
复制代码
⑥平移哈希使得p12 = first bucket
,再重复进行一下相似④⑤⑥的操做—— 防止不断循环的过程当中多线程并发,正好缓存更新了。若是bucket->sel == 0
走CheckMiss
,若是bucket == buckets
走JumpMiss
,本质是同样的
.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
复制代码
当NORMAL
时,CheckMiss
和JumpMiss
都走__objc_msgSend_uncached
⑦__objc_msgSend_uncached
调用MethodTableLookup
⑧保存参数调用c++方法进入慢速流程(准备好装备和药水打BOSS)
总结:方法查找的快速流程
能够和cache_t::find
方法对比加深理解
汇编
__class_lookupMethodAndLoadCache3
与c++中_class_lookupMethodAndLoadCache3
相对应
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
复制代码
// initialize = YES , cache = NO , resolver = YES
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) {
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// 缓存查找,cache为NO直接跳过
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.
// lock是为了防止多线程操做; 类是否被编译
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.
// 从缓存里面查找一遍,如有直接goto done
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
// 造成局部做用域,避免局部变量命名重复
{
// 在类的方法列表中查找方法,如有直接cache_fill
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.
// 在父类缓存中查找,如有直接cache_fill
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.
// 在父类的方法列表中查找方法,如有直接cache_fill
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;
}
复制代码
慢速流程主要分为几个步骤:
①_class_lookupMethodAndLoadCache3
调用lookUpImpOrForward
,此时参数initialize=YES cache=NO resolver=YES
②runtimeLock.lock()
为了防止多线程操做
③realizeClass(cls)
为查找方法作准备条件,若是类没有初始化时,初始化类和父类、元类等
④imp = cache_getImp(cls, sel)
为了容错从缓存
中再找一遍,如有goto done⑨
⑤// Try this class's method lists
局部做用域中,在类的方法列表
中查找方法,如有直接log_and_fill_cache
并goto done⑨
⑥// Try superclass caches and method lists
局部做用域中,遍历父类:先在父类缓存
中查找,如有直接log_and_fill_cache
并goto done
;没有再去父类的方法列表中
查找方法,如有直接log_and_fill_cache
并goto done⑨
⑦若是还没找到就动态方法解析_class_resolveMethod
,标记为triedResolver = YES(已自我拯救过)
并跳转慢速流程④
⑧若是动态方法解析以后仍然没找到imp
,就_objc_msgForward_impcache
获得imp
并cache_fill
⑨done
:多线程解锁,返回imp
接下来拆解步骤进行说明:
cache_getImp
这个方法后续会解释getMethodNoSuper_nolock
遍历调用search_method_list
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;
}
复制代码
search_method_list
利用二分查找寻找方法static method_t *search_method_list(const method_list_t *mlist, SEL sel) {
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
// 若是方法列表已经排序好了,则经过二分查找法查找方法,以节省时间
return findMethodInSortedMethodList(sel, mlist);
} else {
// Linear search of unsorted method list
// 若是方法列表没有排序好就遍历查找
for (auto& meth : *mlist) {
if (meth.name == sel) return &meth;
}
}
#if DEBUG
// sanity-check negative results
if (mlist->isFixedUp()) {
for (auto& meth : *mlist) {
if (meth.name == sel) {
_objc_fatal("linear search worked when binary search did not");
}
}
}
#endif
return nil;
}
复制代码
findMethodInSortedMethodList
二分查找算法的具体实现(了解便可)static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list) {
assert(list);
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
// >>1 表示将变量n的各个二进制位顺序右移1位,最高位补二进制0
// count >>= 1 若是count为偶数则值变为(count / 2);若是count为奇数则值变为(count-1) / 2
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
// 取出中间method_t的name,也就是SEL
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
// 继续向前二分查询
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
// 取出 probe
return (method_t *)probe;
}
// 若是keyValue > probeValue 则折半向后查询
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
复制代码
log_and_fill_cache
->cache_fill
->cache_fill_nolock
进行缓存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_resolveMethod
动态方法解析——在找不到imp时的自我拯救操做
cls
是元类的话说明调用类方法,走_class_resolveInstanceMethod
;非元类的话调用了实例方法,走_class_resolveInstanceMethod
SEL_resolveInstanceMethod
消息,系统调用resolveInstanceMethod
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);
}
}
}
复制代码
_objc_msgForward_impcache
在汇编中调用了_objc_msgForward
,而后又进入_objc_forward_handler
,它在c++调用了objc_defaultForwardHandler
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
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);
}
复制代码
原来unrecognized selector sent to instance xxx
是这么来的啊...
OC的消息机制分为三个阶段:
本文主要讲了方法查找流程
,顺带提了几句动态方法解析
,下一篇文章将经过案例来详细解读动态方法解析
并着重介绍消息转发机制
最后准备了一份动态方法决议的Demo,有兴趣的小伙伴们能够本身下断点看看方法查找流程
和研究下动态方法决议