美文网首页
runtime - objc_msgSend

runtime - objc_msgSend

作者: Breezes | 来源:发表于2020-04-25 23:34 被阅读0次

oc的方法最后都以objc_msgSend()方式发送。

通过代码的方式查看一下:

TestViewController *test = [[TestViewController alloc] init];

通过clang命令将当前类编译成c++文件查看:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_d037d6_mi_0);

        TestViewController *test = ((TestViewController *(*)(id, SEL))(void *)objc_msgSend)((id)((TestViewController *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TestViewController"), sel_registerName("alloc")), sel_registerName("init"));
    }
    return 0;
}

alloc方法会被编译成:

(void *)objc_msgSend)((id)objc_getClass("TestViewController"), sel_registerName("alloc")

init方法:

((id)((TestViewController *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TestViewController"), sel_registerName("alloc")), sel_registerName("init"))

objc_msgSend(receiver,SEL,arg1,arg2);
receiver:消息发送者;
SEL:方法;
arg:方法参数

objc_msgSend的发送流程

消息发送流程分为三个阶段:
1.消息发送;
2.动态解析;
3.消息转发;

整个流程引用一张图说明一下: objc_msgSend.jpg

消息发送机制比较好理解,这里重点说一下动态解析、消息转发:

动态方法解析:

当一个方法无法从当前类,父类中找到的时候,就会触发动态解析流程;何为动态解析:objc运行时会调用+resolveInstanceMethod: 或者 +resolveClassMethod:,让你有机会提供一个函数实现,如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程。
通过runtime源码查看实现:

// 未找到实现。尝试方法解析器一次。

    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 
        //发生变化,重启方法查找流程
        triedResolver = YES;
        goto retry;
    }
/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
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);
        }
    }
}

继续查看 _class_resolveInstanceMethod(cls, sel, inst);的实现:

/***********************************************************************
* _class_resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    //这里查找元类的原因是因为+(void)resolveInstanceMethod 是一个类方法,而类方法是存储在元类中的
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    //调用cls的+(void)resolveInstanceMethod方法。
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
}

我们继续看一下调用+(void)resolveInstanceMethod后,新的方法是怎么替换老的方法的:

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    class_addMethod(self.class, sel, (IMP)dynamicMethodIMP, "@@:");
    BOOL result = [super resolveInstanceMethod:sel];
    result = YES;
    return result;
}

继续查看class_addMethod的实现:

class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return NO;

    mutex_locker_t lock(runtimeLock);
    return ! addMethod(cls, name, imp, types ?: "", NO);
}

addMethod的实现:

addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
    IMP result = nil;

    runtimeLock.assertLocked();

    checkIsKnownClass(cls);
    
    assert(types);
    assert(cls->isRealized());

    method_t *m;
    if ((m = getMethodNoSuper_nolock(cls, name))) {
        // already exists
        if (!replace) {
            result = m->imp;
        } else {
            result = _method_setImplementation(cls, m, imp);
        }
    } else {
        // fixme optimize
        method_list_t *newlist;
        newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
        newlist->entsizeAndFlags = 
            (uint32_t)sizeof(method_t) | fixed_up_method_list;
        newlist->count = 1;
        newlist->first.name = name;
        newlist->first.types = strdupIfMutable(types);
        newlist->first.imp = imp;

        prepareMethodLists(cls, &newlist, 1, NO, NO);
        cls->data()->methods.attachLists(&newlist, 1);
        flushCaches(cls);

        result = nil;
    }

    return result;
}

原来是替换了method_list_t的方法指针。
以后再次查找此方法时,就会通过lookUpImpOrForward这段代码进行查找:

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

到了这里动态方法解析就已经做完了。如果已经实现了+(void)resolveInstanceMethod,那么新实现的方法就会替换旧的未实现的方法,如果未实现+(void)resolveInstanceMethod方法,那么接下来就会进入到消息转发的流程。

消息转发

当动态方法解析失败后会启动消息转发流程,消息转发,顾名思义:就是把该消息转发出去,转发给其他对象,让目标对象实现此方法,消息转发分为两类,一类是快速转发,一类是普通转发。

Fast forwarding

如果目标对象实现了 -forwardingTargetForSelector:,Runtime会在这个时候调用,给你把这个消息转发给其他对象的机会。只要这个方法返回的不是nil或self,整个消息发送的过程就会被重启,当然发送的对象就会变成你返回的那个对象。否则就会继续Normal Forwarding。这里叫Fast,只是为了区别下一步的消息转发,因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对快点。
继续从源码了解消息转发的实现,
在之前,先看一下lookUpImpOrForward方法的注释:

/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup. 
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known. 
*   If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use 
*   must be converted to _objc_msgForward or _objc_msgForward_stret.
*   If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/

lookUpImpOrForward方法:

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

这时候的imp会变成 _objc_msgForward_impcache_objc_msgForward_impcache只是一个内部的函数指针,上面的注释里提到,_objc_msgForward_impcache需要被转换为_objc_msgForward_objc_msgForward_stret才能被外部调用,转换过程是汇编实现,而 _objc_msgForward_impcache就是汇编实现的静态入口:

    STATIC_ENTRY __objc_msgForward_impcache
    // Method cache version

    // THIS IS NOT A CALLABLE C FUNCTION
    // Out-of-band Z is 0 (EQ) for normal, 1 (NE) for stret

    beq __objc_msgForward
    b   __objc_msgForward_stret
    
    END_ENTRY __objc_msgForward_impcache

至于转换称_objc_msgForward还是_objc_msgForward_stret是通过EQ\NE决定的:
EQ = Equal:_objc_msgForward
NE = NotEqual:_objc_msgForward_stret
此时转换完成imp指针就会被替换成_objc_msgForward\ _objc_msgForward_stret调用。
此时我们只需要在代码中实现:

- (id)forwardingTargetForSelector:(SEL)aSelector {
    id result = [super forwardingTargetForSelector:aSelector];
    result = self.objectTest;
    return result;
}

self.objectTest一定是实现了被转发的方法,不然会继续走下一步,当此方法被实现了后,整个消息发送的过程就会被重启;

Normal forwarding

此过程比上一步稍微慢一些是因为这个过程的消息转发会创建一个NSInvocation对象,
这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector返回nil,Runtime会直接发出-doesNotRecognizeSelector:消息,程序这时就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    [super methodSignatureForSelector:aSelector];
    NSMethodSignature *initSignature = [NSMethodSignature signatureWithObjCTypes:"@@:"];
    return initSignature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL selector = [anInvocation selector];
    if ([self.objectTest respondsToSelector:selector]) {
        [anInvocation setTarget:self.objectTest];
        [anInvocation invoke];
    }
}

此过程的消息转发可以帮助我们轻松获取某一个方法的所有参数,比如著名的JSPatch就是通过消息转发的方法获取方法的参数,

相关文章

网友评论

      本文标题:runtime - objc_msgSend

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