iOS底层探索九(方法的本质中--objc_msgSend慢速及方法转发初探)

15 篇文章 0 订阅
13 篇文章 0 订阅

前言

相关文章

       iOS底层探索二(OC 中 alloc 方法 初探)

       iOS底层探索三(内存对齐与calloc分析)  

iOS底层探索四(isa初探-联合体,位域,内存优化)     

iOS底层探索五(isa与类的关系)  

iOS底层探索六(类的分析上)

iOS底层探索七(类的分析下)

iOS底层探索八(方法本质上)

iOS底层探索十(方法的本质下-消息转发流程)

  相关代码:
      objc4_752源码 

     上篇文章讲述了方法的本质实际就是调用objc_msgSend,然后经过汇编代码进行快速调用查找,或者根据C代码慢速查找;

汇编代码快速查找,已经在源码中做了详细的注释,并且在其中加了一个objc_msgSend反汇编伪代码,可以配合汇编代码一起查看大家可以自行查看;

方法在类中的查找流程

首先我们来看一下代码来回顾一下之前的东西:查看Demo

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        #pragma clang diagnostic push
//        // 让编译器忽略错误,不然调用没有的方法,编译器编译完语法分析会报错,导致不能运行
        #pragma clang diagnostic ignored "-Wundeclared-selector"

                XZTeacher *teacher = [[XZTeacher alloc] init];
                // 对象方法
                // 自己有 - 返回自己
                [teacher sayHello];
                // 自己没有 - 老爸 -
                [teacher sayNB];
//                // 自己没有 - 老爸没有 - NSObject
                [teacher sayMaster];
                // 自己没有 - 老爸没有 - NSObject 没有
// unrecognized selector sent to instance 0x101806f30 调用下面会直接报错
//               [teacher performSelector:@selector(saySomething)];
                
                // 类方法
                // 自己有 - 返回自己
                [XZTeacher sayObjc];
//                // 自己没有 - 老爸 -
                [XZTeacher sayHappay];
//                // 自己没有 - 老爸没有 - NSObject
                [XZTeacher sayEasy];
                // 自己没有 - 老爸没有 - NSObject 没有
                // unrecognized selector sent to instance 0x100001368
//                 [XZTeacher performSelector:@selector(sayLove)];
                
                //  sayMaster]这个是NSObject中的实例方法
                 [XZTeacher performSelector:@selector(sayMaster)];

                
        #pragma clang diagnostic pop
    }
    return 0;
}

我们来看一下这个方法的打印结果:

我们可以看到,我们使用[XZTeacher performSelector:@selector(sayMaster)],调用NSObject中的实例方法,也调用成功了;这个实际上,我们在之前的文章中也有提到过什么原因,这里再来回顾一下,首先类方法实际就是在元类中以实例方法的方式进行存储的,而元类的根源类,也是继承于NSObject的,所以在方法查找的过程中,类方法会最终查找到NSObject类的实例方法,找到并执行了;但是还有一部分方法是在本类,以及父类都找不到的时候就会崩溃,下面我们就进行分析一下

就是根据上图的查找结果,最终会找到sayMaster并执行

这个我们可以进行总结一下:

对象的实例方法:

  1. 自己有

  2. 自己没有-->父类有

  3. 自己没有-->父类没有-->父类的父类----直到NSObject

  4. 自己没有-->父类没有-->直到NSObject也没有--->崩溃

对象的类方法:

  1. 自己有

  2. 自己没有-->父类有

  3. 自己没有-->父类没有-->父类的父类----直到NSObject

  4. 自己没有-->父类没有-->直到NSObject类方法没有-->NSObject实例方法

  5. 自己没有-->父类没有-->直到NSObject类方法没有-->NSObject实例方法没有-->崩溃

方法的慢速查找流程

得到上面的结论之后,我们从底层源码进行分析,首先我们调用方法同过objc_msgSend 查找cachet-->进入汇编快速查找-->最终进入代码_class_lookupMethodAndLoadCache3 慢速查找,由于都是新方法,所以我们可以直接进入方法最后进行查看

我们直接查看_class_lookupMethodAndLoadCache3方法源码

//这里的extern  标示外部也可以调用这个方法
extern IMP _class_lookupMethodAndLoadCache3(id, SEL, Class);

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
/**
 obj 当前对象
 sel 方法名称
 cls 实例方法为类    类方法为元类
 NO   cache 没有缓存才会进入这里,有的话,从缓存哪里就返回出去了
 */
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

我们可以看出,这个方法中,会调用lookUpImpOrForward,并传入几个参数cls(当前类,如果是类方法这里就是元类)sel 方法编号,obj (当前对象)以及initialize(YES)cache(NO),resilver(NO),继续查看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
//    查找方法进入的时候这里是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.
//加锁,防止多线程访问
    runtimeLock.lock();
//   内部对class 的判断,是否被编译
    checkIsKnownClass(cls);

    // 这里是拿出类中的bits中的 class_rw_t 中的 data中的Method查看是否有
    //为查找方法做准备条件,判断类有没有加载好,如果没有加载好,那就先加载一下类信息,准备好父类、元类
    if (!cls->isRealized()) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }
    //确定类已经加载完成,
    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
    }


 retry:    
    runtimeLock.assertLocked();

    // Try this class's cache.
  //判断该方法从缓存里面查找一遍,如果是方法调用,会先走cache,一般这里imp 是取不到的,别的方法可能会调用这里
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class's method lists.
//    这里的括号是为了行程局部作用域,避免局部变量命名重
    {
//        在MethodList 中进行查找方法
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
//            找到方法后,填充到缓存中去,内部调用cache_fill_nolock
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
      //直接跳到done这个点,C语言调用方式goto
            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 {
//如果是_objc_msgForward_impcache方法,就退出,不需要遍历了,因为_objc_msgForward_impcache这个方法就是未实现时候转发用的
                    // 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();
    
//     实例方法解析:resolveInstanceMethod
//     类方法解析:resolveClassMethod
        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.
        //设置为NO,防止因为消息动态解析导致死循环
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.
    //如果第一次转发还是没用的话,就取出_objc_msgForward_impcache这个方法指针
    imp = (IMP)_objc_msgForward_impcache;
    //缓存起来将指针返回回去执行
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlock();

    return imp;
}

这个方法就有几个重点要观察的:

  1. runtimeLock.lock()为了防止多线程操作

  2. realizeClassMaybeSwiftAndLeaveLocked 为查找方法做准备条件,如果类没有初始化时,初始化类和父类、元类、分类等

  3. imp = cache_getImp(cls, sel)为了容错从缓存中再找一遍,如果找到直接goto done(8)

  4. 局部作用域中查找本类getMethodNoSuper_nolock中,如果找到进行log_and_fill_cache 保存缓存直接goto done(8)

  5. 局部作用与中在父类中进行查找首先父类缓存中查找,若有直接log_and_fill_cachegoto done;没有再去父类的方法列表中查找方法,若有直接log_and_fill_cachegoto done(8)

  6. 如果还没找到就动态方法解析_class_resolveMethod,标记为triedResolver = YES(已自我拯救过),动态方法解析结束后跳转慢速流程第三步进行再一次查找

  7. 如果动态方法解析之后再找一遍仍然没找到imp,就抛出错误_objc_msgForward_impcache得到impcache_fill

  8. done:多线程解锁,返回imp

这个方法cache_getImp 后续进行详细解释

getMethodNoSuper_nolock 方法

这里对其中部分代码进行解释第四步getMethodNoSuper_nolock 方法遍历调用search_method_list方法

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

其中在search_method_list过程之后,通过findMethodInSortedMethodList(sel, mlist)进行二分查找,保证能够快速寻找到目标方法

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);
        
        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--;
            }
            return (method_t *)probe;
        }
        // 如果keyValue > probeValue 则折半向后查询
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

log_and_fill_cache -->cache_fill--->cache_fill_nolock连续调用这个方法可以看类的分析下

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);
}
void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
#if !DEBUG_TASK_THREADS
    mutex_locker_t lock(cacheUpdateLock);
    cache_fill_nolock(cls, sel, imp, receiver);
#else
    _collecting_in_critical();
    return;
#endif
}

_class_resolveMethod动态方法解析:在找不到imp时的自我拯救操作 ,下面会进行分析

 

_objc_msgForward_impcache方法

这个方法中就是如果方法在本类,父类都找不到方法后执行到这里_objc_msgForward_impcache 方法

extern void _objc_msgForward_impcache(void);//这个方法会进入汇编


	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] //调用_objc_forward_handler 方法
	TailCallFunctionPointer x17
	
	END_ENTRY __objc_msgForward
	
	

_objc_msgForward_impcache这个方法调用到汇编之后,汇编方法又会调用会C方法_objc_forward_handler

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;

这里我们就能看到熟悉的unrecognized selector sent to instance 崩溃信息了

消息转发机制初探

我们已经看到了如果没有实现方法,然后调用,系统会直接崩溃,那么如何避免这些问题呢,苹果大大给我们提供了另一个解决思路,就是消息转发机制,这篇我们先进行一个初步的查看

首先我们现在main函数中调用一个没有实现的方法

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        #pragma clang diagnostic push
//        // 让编译器忽略错误,不然调用没有的方法,编译器编译完语法分析会报错,导致不能运行
        #pragma clang diagnostic ignored "-Wundeclared-selector"

                XZTeacher *teacher = [[XZTeacher alloc] init];
//        消息转发流程这个方法.h中之声明,不实现
        [teacher saySomthing];
        
        #pragma clang diagnostic pop
    }
    return 0;
}

根据上面文章的分析,我们很快可以定位到,如果都没有找到这个方法,那么就会走到动态解析方法_class_resolveMethod

static void resolveMethod(Class cls, SEL sel, id inst)
{
    runtimeLock.assertUnlocked();
    assert(cls->isRealized());

    if (! cls->isMetaClass()) {//不是元类
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // 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方法

static void resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    runtimeLock.assertUnlocked();
    assert(cls->isRealized());
//查找方法中是否有实现resolveInstanceMethod这个方法
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }
    //发送SEL_resolveInstanceMethod消息,
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    //发送消息SEL_resolveInstanceMethod 传参为我们的方法编号
    //崩溃的方法不是这个方法,说明再NSObject方法中有实现这个方法
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
//再次查找类中是否sel方法,因为resolveInstanceMethod方法执行后可能动态进行添加了,resolver是不要进行消息转发了
    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));
        }
    }
}

首先判断类有没有实现SEL_resolveInstanceMethod方法,其实也就是+ (BOOL)resolveInstanceMethod:(SEL)sel方法,我们通过在源码搜索可以看到在NSObject类中已经都实现了,并且都是一个空方法,并且返回NO,

//在NSObject.mm 文件中有看到这个方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}

在lookUpImpOrNil 方法中实际就是有调用了一次方法查找流程这里的参数是NO( initialize)  YES (Cache ) NO (resolver)不会造成递归

IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}

这里我们看到系统会自动调用+resolveInstanceMethod方法,这说明我们就有一些可操作性,那么我们在类中实现以下这个方法

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@"XZTeacher 走了resolveInstanceMethod %p",sel);
    return [super resolveInstanceMethod:sel];
}

运行程序看到输出结果,我们可以看到执行了我们输出日志,但是崩溃了,这里我们就考虑我们是可以做一些操作


        这里我们可以考虑到这里实际上的一些列操作就是为了找到函数的IMP ,那我们是不是可以直接在这里将函数IMP实现使得下面lookUpImpOrNil方法的时候可以找到IMP,程序就不会崩溃了;

具体实现方法,引入头文件#import <objc/message.h> 如下所示:

#import <objc/message.h>

- (void)sayHello{
    NSLog(@"%s",__func__);

}

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(saySomthing)) {
        NSLog(@"进入方法了");
        IMP saySomeIMP = class_getMethodImplementation(self, @selector(sayHello));
        Method sayMethod = class_getInstanceMethod(self, @selector(sayHello));
        const char *sayType = method_getTypeEncoding(sayMethod);
        return class_addMethod(self, sel, saySomeIMP, sayType);
    }
    return [super resolveInstanceMethod:sel];
}

我们来看一下输出结果:

可以看出我们在调用saySomthing 方法的时候进入了我们写的代码,并且还调用了我们想让他调用的sayHello方法,而且没有崩溃;

 

总结

    这篇文章主要是根据上篇文章职工objc_msgSend方法快速查找流程汇编过程找到_class_lookupMethodAndLoadCache3 方法,并进行分析了这个代码中消息查找的流程,并对消息转发防止崩溃进行了初步的了解,如果有错误的地方还请指正,大家一起讨论,(文章中有错误后,我发现会第一时间对博文进行修改),还望大家体谅!

欢迎大家点赞,关注我的CSDN,我会定期做一些技术分享!

写给自己

时光流逝,讨厌的人会带着讨人厌的话离开,喜欢的人会带着美好的事到来。把目光放在远处,洒脱还给自己。忘掉路人,放过自己,未完待续...

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值