iOS 消息转发

前言

OC是个运行时语言,它的运行时主要靠runtime库。而一个对象调用一个方法的时候,实际上是向对象发送一个消息。那么,若是找不到对应的方法的话,会发生什么现象呢?

方法查询

从上一篇我们可以看到方法的查询是沿着supclass向源头查找的,而最后找不到的话才会进入消息转发流程。那么方法的查询是哪个函数呢?


IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)

这个函数用于方法的查询,它的内部实现进行精简的话是这样的:

1.查询方法缓存

// 查询方法缓存
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

2.沿着superclass查找方法列表


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

3.查找不到方法,动态方法解析(resolveMethod)


if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        
        //方法处理
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        
        // 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;
    }
    

4.重定向

既没有实现,又没有处理,则Use forwarding。


imp = (IMP)_objc_msgForward_impcache;
//将方法添加到缓存中
cache_fill(cls, sel, imp, inst);
    

到这里还没进入消息转发的流程,这里的处理主要是查找对应的方法。那么关于网上所说的消息转发是怎么了解到的呢?我觉得是看官方文档了解到的。(我实在没找到相应的源码_)

消息转发

根据文档的描述,当上面的函数还是没找到消息的话就会进入消息转发流程。
消息转发流程有两个重要的方法:


- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");
- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");
- 

具体流程如下:

 

当之前的forwoardingTargetForSelector找不到转发对象的时候,才会进入之后的消息转发流程。

先进行方法的签名methodSignatureForSelector,将方法包装秤NSInvocation对象。
然后才进入正式的转发流程forwardInvocation,在这里可以进行方法的最终处理。

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([someOtherObject respondsToSelector:
            [anInvocation selector]])
        [anInvocation invokeWithTarget:someOtherObject];
    else
        [super forwardInvocation:anInvocation];
}

当然,到了消息转发的步骤后,消耗的资源就比较多了。但是使用消息转发机制可以做到一个很好的防御策略,用来防止查不到对应的方法造成的崩溃。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值