美文网首页Objective-C底层原理
IOS底层原理之动态方法解析和消息转发

IOS底层原理之动态方法解析和消息转发

作者: 风紧扯呼 | 来源:发表于2019-12-29 16:55 被阅读0次

一、前言

在OC中方法的调用都是转化为objc_msgSend函数的调用的。在我上一篇文章深入汇编探索objc_msgSend中对objc_msgSend底层的汇编源码进行了分析,已然知道了方法发送流程。

1、首先从消息接受者receiverClass的cache中查找方法,如果找到方法,则直接调用。
2、如果在receiverClass的cache中没有找到方法,则从receiverClass的方法列表中查找方法,如果找到方法则将方法缓存到cache并调用方法。
3、如果在receiverClass的方法列表中没有找到,则从receiverClass父类的缓存中查找,如果在缓存中有找到,则会先判断是否是消息转发的方法。
4、如果是消息转发的方法则会走消息转发的流程,终止方法的查找。
5、如果是非消息转发的方法则会调用log_and_fill_cache进行方法的缓存,终止方法的查找并调用方法。
6、如果在父类的缓存中没有找到,则会从父类的方法列表中查找,如果找到了则会调用log_and_fill_cache进行方法的缓存,终止方法的查找并调用方法。
7、如果在父类的方法列表中没有找到,重复执行3、5、6步骤,直到父类为nil为止。
8、如果直到父类为nil还是未能找到方法的实现,则会走动态方法解析流程。

下面分析动态方法解析和消息转发。

二、动态方法解析

在上一篇文章深入汇编探索objc_msgSend中,方法的查找流程在lookUpImpOrForward方法中实现,在消息接受者receiverClass本身及其父类都未实现方法情况下,开始动态方法解析。动态方法解析其实是苹果程序员的一种容错手段。下面我们来看下lookUpImpOrForward方法中动态方法解析部分的代码。

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

在这段代码中调用了_class_resolveMethod方法,下面是_class_resolveMethod方法的源码。

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

这里有一个判断当前类cls是否是元类的条件,同时我们知道对象方法是存储类中的,类方法存储在元类中,由此看这里的判断并不奇怪,用以区分对象方法和类方法的动态解析。

1、对象方法的动态解析

我们先来看看对象方法的动态解析。以下是对象方法动态解析_class_resolveInstanceMethod的源码。

static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    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);

    // 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*/);
  ...
}

在这段代码中调用了lookUpImpOrNil方法,传入cls的元类(cls->ISA()),为什么这里要传元类而不是cls本身呢?我们来看下lookUpImpOrNil方法的实现。

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

lookUpImpOrNil方法内部其实调用了lookUpImpOrForward方法,而lookUpImpOrForward方法是查找方法的代码实现。在这里查找的是resolveInstanceMethod这样的一个系统约定的方法,这个方法在NSObject中有实现,而且是一个类方法,这就是为什么传入元类而非当前类的原因。

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}

接下来调用了objc_msgSend向当前类cls中发送了一个消息,如果消息发送成功则说明当前类中重写了NSObject的resolveInstanceMethod方法,再次调用lookUpImpOrForward方法重新执行查找方法实现的流程。

2、类方法的动态解析

以下是类方法动态解析_class_resolveClassMethod的源码。

static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
    assert(cls->isMetaClass());
    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                        SEL_resolveClassMethod, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
}

其实类方法动态解析和对象方法动态解析的流程是一模摸一样的,不同的是类方法动态解析是在resolveClassMethod上面做文章的,而且在类方法动态解析未做处理的时候会走对象方法动态解析的流程,这是因为类方法存在元类中,而NSObject的元类继承自NSObject,所以在类方法的巡查过程中,通过元类的继承关系可能会最终找到NSObject类。

 _class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,  NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
{
    _class_resolveInstanceMethod(cls, sel, inst);
}

3、动态方法解析实践

@interface Person : NSObject
- (void)sayHappy;
+ (void)sayLove;
@end

@implementation Person

@end

上面这段代码定义一个Person类,在该类中定义一个对象方法sayHappy和一个类方法sayLove,这两个方法都未实现,那么我们在调用这个两个方法的时候程序必定会崩溃。那么只需要在Person类中重写resolveInstanceMethodresolveClassMethod方法去实现动态方法解析,程序便不会崩溃。

@implementation Person

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if(sel == @selector(sayHappy)){
        IMP sayHappyImp = class_getMethodImplementation(self, @selector(happy));
        Method happyMethod = class_getInstanceMethod(self, @selector(happy));
        const char *types = method_getTypeEncoding(happyMethod);
        class_addMethod(self, sel, sayHappyImp, types);
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}


+ (BOOL)resolveClassMethod:(SEL)sel{
    if(sel == @selector(sayLove)){
        Class class = object_getClass(self);//元类
        IMP sayLoveImp = class_getMethodImplementation(class, @selector(love));
        Method loveMethod = class_getClassMethod(class, @selector(love));
        const char *types = method_getTypeEncoding(loveMethod);
        class_addMethod(class, sel, sayLoveImp, types);
        return YES;
    }
    return [super resolveClassMethod:sel];
}

-(void)happy{
    NSLog(@"I am happy!");
}

+(void)love{
    NSLog(@"love you!");
}

@end

这样一来sayHappy方法的调用会转化为happy方法的调用,sayLove方法的调用会转化为love方法的调用。


三、消息转发

如果一个方法的调用在消息分发阶段没有找到对应的方法也未做动态方法解析处理,这个时候就会走消息转发流程。

imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);

1、消息转发流程

先来看一下消息转发的流程。


1、调用forwardingTargetForSelector方法,如果返回值不为nil,则调用objc_msgSend进行消息发送。
2、如果forwardingTargetForSelector方法返回nil,则会调用methodSignatureForSelector方法获取方法签名,如果返回nil则说明消息无法处理,调用doesNotRecognizeSelector方法
3、如果调用methodSignatureForSelector方法成功获取方法签名,则调用forwardInvocation方法,开发者可以在forwardInvocation方法中自定义逻辑。
4、这里只是关于对象方法的消息转发,类方法的消息转发流程是一样的。

2、消息转发实践

如之前代码所示,在Person类中并没有实现sayHappy这个方法,如果在没有动态方法解析处理的情况下,调用这个方法程序必定会崩溃,但是如果实现了消息转发的处理,那么就可以将方法的调用转交其他的对象来处理。

定义一个类OtherHelper,定义一个sayHappy方法并实现该方法。

@interface OtherHelper : NSObject
-(void)sayHappy;
@end

@implementation OtherHelper
- (void)sayHappy{
    NSLog(@"happly:%s",__func__); 
}
@end

然后再Person类中进行消息转发处理。

@implementation Person

- (id)forwardingTargetForSelector:(SEL)aSelector{
    OtherHelper *helper = [[OtherHelper alloc]init];
    if([helper respondsToSelector:aSelector]){
        return helper;
    }
    return [super forwardingTargetForSelector:aSelector];
}

@end

在Person类中重写forwardingTargetForSelector方法,返回OtherHelper的实例对象,这样Person的sayHappy方法调用就会转交OtherHelper的sayHappy处理。

如果forwardingTargetForSelector方法返回nil,那么还有可以重写methodSignatureForSelectorforwardInvocation进行消息转发处理。

@implementation Person

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
   if(aSelector == @selector(sayHappy)){
//        return [[OtherHelper alloc]methodSignatureForSelector:aSelector];
       return [NSMethodSignature signatureWithObjCTypes:"v@:"];
   }
   return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
   NSLog(@"forwardInvocation");
   OtherHelper *helper = [OtherHelper new];
   if([helper respondsToSelector:anInvocation.selector]){
       [anInvocation invokeWithTarget:helper];
   }else{
       [super forwardInvocation:anInvocation];
   }
}

@end

相关文章

网友评论

    本文标题:IOS底层原理之动态方法解析和消息转发

    本文链接:https://www.haomeiwen.com/subject/apmsoctx.html