runtime - 消息机制

消息机制是我见过runtime里面最常见的面试题,几乎问到runtime,必问消息机制

换句话说就是[self test]; 这个函数调用,是怎么找到底层函数列表的test函数,首先这段代码的底层是c++转成C++调用的大约是objc_msgSend(self,sel_registerName(test))这种形式,所以

第一步:消息发送阶段

objc_msgSend(self,sel_registerName(test)),给对应的类发送发送消息,前面的是类,后面的是函数最终结果是一个SEL,所以转化一下这个代码应该是objc_msgSend(class的类对象 ,SEL)

然后进到lookUpImpOrForward,代码如下

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }

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

    // We don't want people to be able to craft a binary blob that looks like
    // a class but really isn't one and do a CFI attack.
    //
    // To make these harder we want to make sure this is a class that was
    // either built into the binary or legitimately registered through
    // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
    //
    // TODO: this check is quite costly during process startup.
    checkIsKnownClass(cls);

    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }

    if (slowpath((behavior & LOOKUP_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
    }

    runtimeLock.assertLocked();
    curClass = cls;

    // The code used to lookpu the class's cache again right after
    // we take the lock but for the vast majority of the cases
    // evidence shows this is a miss most of the time, hence a time loss.
    //
    // The only codepath calling into this without having performed some
    // kind of cache lookup is class_getInstanceMethod().

    for (unsigned attempts = unreasonableClassCount();;) {
        // curClass method list.
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            imp = meth->imp;
            goto done;
        }

        if (slowpath((curClass = curClass->superclass) == nil)) {
            // No implementation found, and method resolver didn't help.
            // Use forwarding.
            imp = forward_imp;
            break;
        }

        // Halt if there is a cycle in the superclass chain.
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // Superclass cache.
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
            // Found a forward:: entry in a superclass.
            // Stop searching, but don't cache yet; call method
            // resolver for this class first.
            break;
        }
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }

    // No implementation found. Try method resolver once.

    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    log_and_fill_cache(cls, imp, sel, inst, curClass);
    runtimeLock.unlock();
 done_nolock:
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

感兴趣的可以详细看看大致解释下,首先是从缓存列表取函数,如果得到了直接返回,然后是访问函数列表,然后访问superclass的cache,然后是函数列表,如果还是没找到,则进入动态方法解析阶段

第二步:动态方法解析

第一步走完了即使没找到也不会直接崩溃,会进入动态方法解析的阶段

动态方法解析首先会判断是否还有父类,有的话继续访问cache散列表 + 方法列表,直到没有父类为止,源码如下

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();

    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNil(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}

可以看出上面的代码在递归的同时,访问了一个叫resolveInstanceMethod的函数

/***********************************************************************
* 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 resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, 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(inst, sel, cls);

    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函数,这个函数官方给的解释是Call +resolveInstanceMethod, looking for a method to be added to class cls

调用一个叫resolveInstanceMethod的类方法,找一个函数去添加进类和类的实例

+(BOOL)resolveInstanceMethod:(SEL)sel
{
    
}
+(BOOL)resolveClassMethod:(SEL)sel
{
    
}

实际有两个,一个类方法,一个实例方法,其实差不多,只说实例方法

    id p = [Public sharePublic];
    [p test];
    return 0;
+(BOOL)resolveInstanceMethod:(SEL)sel
{
    if(sel == @selector(test))
    {
        Method method = class_getInstanceMethod(self, @selector(addTest));
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
    }
    return YES;
}

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

当然这里面是已有方法,而不是手动创建的动态方法,动态创建想单独写一个来介绍各种参数的作用

第三步:消息转发

如果不处理方法动态解析就进入了最后的消息转发阶段

这个阶段是不开源的,只能通过汇编来查看,最后是进入了 ___forwarding___这个函数里面,网上有很多伪代码,就是分析出这个的具体实现,但具体实现还是不清楚,-(id)forwardingTargetForSelector:(SEL)aSelector最后是到这里,这是能操作的地方,这个函数的目的就是消息转发(废话),怎么转发呢,

比如我的上述代码是往Public里面发消息去找test函数,但Public包括它的父类NSObjcet都没有,前面的动态解析操作也没做,那么久会在这个方法里面要求指定一个类,把消息转发出去,具体代码实现:

首先定义个Person类,然后实现test函数

#import "Person.h"

@implementation Person
-(void)test
{
    NSLog(@"%s",__func__);
}
@end

然后在Public.m里面加上这个:

-(id)forwardingTargetForSelector:(SEL)aSelector
{
    if(@selector(test) == aSelector)
    {
        return [[Person alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

把原本自己类或者父类需要实现,但没实现的函数,丢给其它类处理,就叫做消息转发

最后输出的结果就是-[Person test]

确实是转发出去了

 总结起来就是上图,不过没有消息转发,在动态解析之后

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
iOS Runtime是一种运行时环境,它是iOS操作系统中的重要组成部分。iOS Runtime开发者提供了一套动态询问、修改和扩展应用程序行为的机制。 首先,iOS Runtime实现了Objective-C语言的动态特性。Objective-C是一种面向对象的编程语言,它具有动态特性,即在运行时能够动态地修改对象的行为。iOS Runtime允许开发者通过运行时系统对类、对象、方法以及属性进行动态操作。例如,开发者可以在运行时为某个类添加新的方法,也可以通过运行时修改类的实例变量。 其次,iOS Runtime还提供了消息发送机制。在Objective-C中,对象之间的通信是通过消息发送来完成的。iOS Runtime负责将消息转发给正确的接收者,并执行相应的方法。这个过程是动态的,开发者可以在运行时动态改变消息的接收者或方法的实现。这为Objective-C语言带来了很高的灵活性。 此外,iOS Runtime还支持方法的交换与替换。开发者可以通过运行时机制,在运行时将一个方法的实现替换为另一个方法的实现,或者交换两个方法的实现。这对于调试、性能监控和代码扩展都是非常有用的。 总结来说,iOS RuntimeiOS操作系统中实现Objective-C语言特性的核心组件。它提供了动态特性的支持,使开发者能够在运行时动态地修改类、对象和方法的行为,实现更加灵活和可扩展的应用程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值