NSObject方法调用过程详细分析

分析OC方法调用过程的博客多如牛毛,为何我还来炒剩饭,缘由:html

  1. 我本身虽然以前也分析过方法调用,可是没有成体系作过笔记,此次至关于本身作一个笔记,便于之后查看。
  2. 网上有详细分析,可是都是基于x86汇编分析的(由于runtime开源的代码能够在macOS上运行起来,更方便分析吧),我只对arm64汇编熟悉,我想应该也有部分同窗跟我同样,因此我基于arm64汇编分析一波~
  3. 我这个是基于最新的runtime源码版本(版本号objc4-756.2,苹果官网的源码),网上分析的大多都是几年前的版本,虽说整个逻辑基本一致,可是仍是有些许不一样。

消息发送、转发流程图

objc_msgSend

声明原型

之前是:git

id _Nullable objc_msgSend(id _Nullable self, SEL _Nonnull op, ...);
复制代码

Xcode11改为了:github

void objc_msgSend(void);
复制代码

修改原型是为了解决:接收方(被调用者)会从调用方传递参数的相同位置和格式中检索参数。也就是说,被调用者必定知道调用者把参数放在什么寄存器/内存,这样就不会取错参数。避免了调用者把参数放在a寄存器,可是被调用者去b寄存器取参数的错误行为。缓存

例如:bash

- (void)log: (float)x {
    printf("%f\n", x);
}
复制代码

由于之前是不定参数,因此objc_msgSend(obj, @selector(log:), (float)M_PI);不会报错,可是 在intel ABI上面,会出错(函数里取得的浮点数是错误的浮点数)。(由于intel ABI中,float跟double在不一样的寄存器里,传一个double,可是函数参数是float,函数从float取值)。这个就是调用者把参数放在a寄存器,被调用者去b寄存器取参数。数据结构

如何继续使用objc_msgSend

显然,苹果不建议咱们直接使用objc_msgSend,可是咱们依然想使用,能够用下面两种方法:多线程

  1. 强制转换:
((void (*)(id, SEL, float))objc_msgSend)(obj, @selector(log:), M_PI);
复制代码

会强制将double转换成float,而后放入float对应的寄存器,被调用者也是去float对应的寄存器取参数。架构

  1. 声明函数指针来调用:
void (*PILog)(id, SEL, float) = (void (*)(id, SEL, float))objc_msgSend;
PILog(obj, @selector(log:), M_PI);
复制代码

虽然上面两种方法都是强制转换objc_msgSend,让咱们能够直接使用objc_msgSend,可是仍是不建议强制转换objc_msgSend。对于某些类型的参数,它在运行时仍可能失败,这就是为何存在一些变体(为了适配不一样cpu架构,好比arm64就不用为返回值是结构体,而专门有objc_msgSend_stret,可是其它cpu架构须要有),例如objc_msgSend_stret,objc_msgSend_fpret,objc_msgSend_fp2ret…… 只要使用基本类型,就应该没问题,可是当开始使用结构体时,或使用long double和复杂类型,就得注意了。app

若是咱们使用[obj log:M_PI]来调用,不过什么平台的ABI,都不会出错,Xcode都会帮咱们准确的翻译好的。因此没有特殊须要,不要直接使用objc_msgSend。ide

消息发送

arm64源码分析

arm64汇编作3件事:

1. GetIsa

struct objc_object {
private:
    isa_t isa;
    ... 
}

struct objc_class : objc_object {
    // isa_t isa;
    Class superclass;
    cache_t cache;             
    class_data_bits_t bits; 
    ...
}

union isa_t {
    Class cls;
    uintptr_t bits;
    struct {
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls (4-36bits,共33bits,存放类地址): 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
    };
    ...
};
复制代码

由于对象的方法存放在对象对应的类里,因此须要得到类的地址。类的地址存放在isa的4-36 bits上,因此须要先得到isa;而对象(地址)放在X0上,对象就是objc_object结构体,因此X0里的地址就是objc_object结构体地址,结构体第一个就是isa,那么X0地址就能够看作是isa地址。因此X0&ISA_MASK(0x0000000ffffffff8ULL)就是类地址(由于类的指针要按照字节(8 bits)内存对齐,其指针后三位一定是0,33(33个1与运算)+3(填充3个0),一共36位表示)。

当X0小于0时候,说明X0是tagged pointer,经过类索引来获取类地址。

CacheLookup

//缓存的数据结构
typedef uint32_t mask_t;
struct cache_t {
    struct bucket_t *_buckets; //哈希表地址
    mask_t _mask;  //哈希表的大小,值为2^n-1
    mask_t _occupied; //哈希表中元素个数
}

typedef uintptr_t cache_key_t;
struct bucket_t {
    cache_key_t _key; //SEL
    IMP _imp;  //函数指针
}
复制代码

先讨论一个数学问题: a%b=a-(a/b)*b,这个很明显吧;那么当b=2^n - 1,好比b=三、七、15等等,a%b=a&b。好比13%3=1,13&3也是等于1。

讲人话,就是当b=2^n - 1,能够用与运算(a&b)来替代模运算(a%b),可是避免了模操做的昂贵开销。汇编里,用sel&mask来代替sel%mask。

sel%mask(哈希表大小)+buckets,结果就是sel函数在缓存里的地址;若是cache里为0,说明没有缓存,调用__objc_msgSend_uncached;若是发生哈希冲突,那么从后往前遍历,若是SEL跟X1匹配上了,则缓存命中;若是遍历到bucket_t的SEL为0,则调用__objc_msgSend_uncached。 X12第一次遍历到buckets(哈希表表头)时,将X12置为哈希表尾,从新从后往前遍历。整个遍历过程若是遇到SEL为0,则调用__objc_msgSend_uncached,X12第二次遍历到buckets时,也调用__objc_msgSend_uncached,遍历过程若是缓存命中,则调用imp,直接ret。

__objc_msgSend_uncached

__objc_msgSend_uncached就是调用前保存X0-X8/q0-q7寄存器,而后调用__class_lookupMethodAndLoadCache3函数,返回函数imp放在x17,恢复寄存器,而后调用imp。

C/C++源码分析

_class_lookupMethodAndLoadCache3

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
复制代码

_class_lookupMethodAndLoadCache3就是调用了lookUpImpOrForward函数而已。

lookUpImpOrForward

lookUpImpOrForward函数主要干:1.类没有注册,就注册类;2.类没有初始化,就初始化类;3.分别从缓存(cache_getImp)和类方法列表(getMethodNoSuper_nolock)里遍历,寻找sel函数;4.循环从父类的缓存和方法列表遍历,直到父类为nil;5.若是尚未找到,则进行方法解析(resolveMethod);6.若是最后依然没有找到方法,就把imp赋值为_objc_msgForward_impcache,返回imp。下面详细分析这几个过程:

注册类

//注册类
if (!cls->isRealized()) {
    cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
    // runtimeLock may have been dropped but is now locked again
}
复制代码

通常状况下,类在App启动加载macho文件时候,就已经注册了。可是也有特例,好比weaklink的类,可能运行到这里,尚未初始化。为何有weaklink,就是App最低版本支持iOS9,可是却使用了iOS11 SDK的新功能,若是没有weaklink,程序里确定是不能使用新版本的功能的。更详细介绍,请见官网

注册类(realizeClassWithoutSwift) 这个过程会申请class_rw_t空间,递归realize父类跟元类,而后设置类的父类跟元类;添加类的方法、属性、协议;添加分类的方法、属性、协议。返回这个类的结构体

初始化类

if (initialize && !cls->isInitialized()) {
    cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    // runtimeLock may have been dropped but is now locked again

    // 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 } 复制代码

若是类没有初始化,先递归初始化父类,而后给这个类发送objc_msgSend(cls, SEL_initialize)方法。因此initialize不须要显示调用父类,而且子类没有实现initialize,会调用父类的initialize方法(这个方法没啥特别的,也是经过objc_msgSend来调用的)。

cache_getImp

retry:    
runtimeLock.assertLocked();

// Try this class's cache. imp = cache_getImp(cls, sel); if (imp) goto done; 复制代码

由于多线程,此时cache可能改变了,因此须要从新来次CacheLookup。

getMethodNoSuper_nolock

// 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; } } 复制代码

getMethodNoSuper_nolock内部会调用search_method_list函数,search_method_list函数就是遍历类的方法列表,只不过当方法列表是排序的,就二分法查找,不然就是依次遍历。

循环遍历父类的cache_getImp跟getMethodNoSuper_nolock

// Try superclass caches and method lists.
{
    unsigned attempts = unreasonableClassCount();
    从上图能够看出,不论是类仍是元类,都是一直遍历到RootClass(NSObject)。
    整个过程,不过是cache中,仍是methodlist中找到sel的imp,都调用log_and_fill_cache,将sel和imp放入cache中
    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.");
        }
        
        // 父类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 {
                // 若是imp为_objc_msgForward_impcache,说明这个sel以前寻找过,没有找到。因此退出循环
                // 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; } } } 复制代码

resolveMethod(方法解析)

// 若是上面都没有找到sel的imp,就不会执行goto done;进而走到这里来,这里会调用方法解析,方法解析后,而后goto retry,
又回到上面的cache_getImp--> getMethodNoSuper_nolock -->
循环遍历父类的cache_getImp跟getMethodNoSuper_nolock -->
再次到此处,可是再次到此处时候,不会进入if里面了,由于triedResolver已经设置为YES了。
if (resolver  &&  !triedResolver) {
    runtimeLock.unlock();
    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;
}


static void resolveMethod(Class cls, SEL sel, id inst)
{
    runtimeLock.assertUnlocked();
    assert(cls->isRealized());
    //若是类不是元类,调用resolveInstanceMethod,
    //resolveInstanceMethod函数会调用objc_msgSend(cls, SEL_resolveInstanceMethod, sel);
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        //若是类是元类,就调用resolveClassMethod
        //resolveClassMethod函数会调用objc_msgSend(nonmeta, SEL_resolveClassMethod, sel);
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            resolveInstanceMethod(cls, sel, inst);
        }
    }
}

//只给出resolveInstanceMethod函数,resolveClassMethod相似。
static void resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    runtimeLock.assertUnlocked();
    assert(cls->isRealized());

    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
    //从这里能够看出执行完SEL_resolveInstanceMethod,返回的bool值,跟会不会进行消息转发无关,仅仅跟打印系统日志有关。
    // 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));
        }
    }
}

复制代码

须要注意是:平时咱们写的消息解析resolveInstanceMethod函数跟resolveClassMethod函数,通常用来add method,他们返回的bool值,跟是否会进入消息转发无关,网上文章绝大部分都说返回YES就表示消息解析已经处理了这个消息,不会进行消息转发,而返回NO,就进入消息转发。实际上是错误的,读者能够本身写demo验证。

根据上面的流程图,咱们能够清楚知道,消息解析后,会从新进行类cache_getImp--> 类getMethodNoSuper_nolock --> 循环遍历父类的cache_getImp跟getMethodNoSuper_nolock,若是找到了,填充cache,而后到done,ret。若是没有找到,imp赋值为_objc_msgForward_impcache,而执行_objc_msgForward_impcache才会进入消息转发,跟resolveInstanceMethod返回的bool值确实没有关系。

_objc_msgForward_impcache

调用_objc_msgForward_impcache:(接口宏,定义在arm64里) 在arm64汇编里,最后调用了_objc_forward_handler函数。 _objc_msgForward-->_objc_forward_handler。

// 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

void objc_setForwardHandler(void *fwd, void *fwd_stret)
{
    _objc_forward_handler = fwd;
#if SUPPORT_STRET
    _objc_forward_stret_handler = fwd_stret;
#endif
}

// Define SUPPORT_STRET on architectures that need separate struct-return ABI.
#if defined(__arm64__)
# define SUPPORT_STRET 0
#else
# define SUPPORT_STRET 1
#endif

由于arm64中(不用为返回值是结构体,而须要支持objc_msgSend_stret(这也是为啥其它文章里面有许多objc_msgSend变体,而本文没有)等。),SUPPORT_STRET为0。
上面代码在arm64中,能够简洁为:

// 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;

void objc_setForwardHandler(void *fwd, void *fwd_stret)
{
    _objc_forward_handler = fwd;
}

能够看到_objc_forward_handler的默认实现是objc_defaultForwardHandler(打印系统日志,杀掉进程),
可是App在启动时候,会调用objc_setForwardHandler,从新给_objc_forward_handler赋值新的函数指针。赋值成什么函数呢?在Core Foundation
中。

复制代码

消息转发阶段

Core Foundation 里面没找到objc_setForwardHandler的调用,可是打符号断点,发现App启动时候,经过_CFInitialize调用了objc_setForwardHandler函数,说明_objc_forward_handler被从新赋值了。

经过消息转发调用堆栈,发现_objc_forward_handler被替换成了_CF_forwarding_prep_0函数,_CF_forwarding_prep_0调用___forwarding___函数。

forwarding 函数(打符号断点看到有336行汇编) 大概作了:

  1. 若是类实现了forwardingTargetForSelector,调用,返回对象target跟self不一样,从新调用objc_msgSend(target,sel...) 而后ret。
  2. 若是实现了methodSignatureForSelector,调用,返回sig,则调用forwardInvocation,而后返回结果;不然调用doesNotRecognizeSelector
// Replaced by CF (throws an NSException)这里说了,
也是被Core Foundation替换,其实也是打日志,抛异常。
+ (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("+[%s %s]: unrecognized selector sent to instance %p", 
                class_getName(self), sel_getName(sel), self);
}

// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
    _objc_fatal("-[%s %s]: unrecognized selector sent to instance %p", 
                object_getClassName(self), sel_getName(sel), self);
}

复制代码

参考

  1. www.mikeash.com/pyblog/objc…
  2. yulingtianxia.com/blog/2016/0…
相关文章
相关标签/搜索