runtime消息处理原理 -- 下篇

上一篇 从api的角度看了看是什么 . https://blog.csdn.net/u014600626/article/details/50864078

这次从源码的角度看看具体是如何实现的.

 Objective-C 是一门动态语言,OC 上所有方法的调用/类的生成都通过 Objective-C Runtime 在运行时进行,我们可以通过类名/方法名反射得到相应的类和方法,也可以替换某个类的方法为新的实现,理论上你可以在运行时通过类名/方法名调用到任何 OC 方法,替换任何类的实现以及新增任意类。今天就来详细解析一下OC中runtime最为吸引人的地方。

标准流程

objc_msgSend函数究竟会干什么事情呢?

  1. Check for ignored selectors (GC) and short-circuit. //  检查是否需要忽略这个sel

  2. Check for nil target. // 检查target是否为nil , 
    If nil & nil receiver handler configured, jump to handler // 如果是nil,有处理nil的handler,到处理handler
    If nil & no handler (default), cleanup and return. // 如果是nil,默认情况, 直接返回
  3. Search the class’s method cache for the method IMP(use hash to find&store method in cache)//从类的方法列表缓存中找到IMP , 用hash表存和取method , 所以时间复杂度为O(1),还是挺快的
    -1. If found, jump to it. // 缓存中找到,执行
    -2. Not found: lookup the method IMP in the class itself corresponding its hierarchy chain.// 缓存中没有找到 , 根据继承链到类对象中寻找
    If found, load it into cache and jump to it. // 类对象或者继承链中有,加入缓存,执行imp
    If not found, jump to forwarding mechanism. // 类对象中没有, 走runtime的消息转发机制

总结一下objc_msgSend会做一下几件事情:
1.检测这个 selector是不是要忽略的。
2.检查target是不是为nil。

如果这里有相应的nil的处理函数,就跳转到相应的函数中。
如果没有处理nil的函数,就自动清理现场并返回。这一点就是为何在OC中给nil发送消息不会崩溃的原因。

3.确定不是给nil发消息之后,在该class的缓存中查找方法对应的IMP实现。

如果找到,就跳转进去执行。
如果没有找到,就在方法分发表里面继续查找,一直找到NSObject为止。

4.如果还没有找到,那就需要开始消息转发阶段了。至此,发送消息Messaging阶段完成。这一阶段主要完成的是通过select()快速查找IMP的过程。

消息发送的全过程, 源码解析

网上有这样一段C版本的objc_msgSend的源码

#include <objc/objc-runtime.h>

id  c_objc_msgSend( struct objc_class /* ahem */ *self, SEL _cmd, ...)
{
   struct objc_class    *cls;
   struct objc_cache    *cache;
   unsigned int         hash;
   struct objc_method   *method;   
   unsigned int         index;
   
   if( self)
   {
      cls   = self->isa;
      cache = cls->cache;
      hash  = cache->mask;
      index = (unsigned int) _cmd & hash;
      
      do
      {
         method = cache->buckets[ index];
         if( ! method)
            goto recache;
         index = (index + 1) & cache->mask;
      }
      while( method->method_name != _cmd);
      return( (*method->method_imp)( (id) self, _cmd));
   }
   return( (id) self);

recache:
   /* ... */
   return( 0);
}

该源码中有一个do-while循环,这个循环就是上一章里面提到的在方法分发表里面查找method的过程。 

不过我在下载的runtime源码中搜索objc_msgSend 有多处实现 , 是针对不同的处理器 i386架构, arm 还有x86 , 找了其中的一段汇编代码。


/********************************************************************
 *
 * id objc_msgSend(id self, SEL	_cmd,...);
 *
 ********************************************************************/
	

	ENTRY	_objc_msgSend
	MESSENGER_START

	NilTest	NORMAL

	GetIsaFast NORMAL		// r11 = self->isa
	CacheLookup NORMAL		// calls IMP on success

	NilTestSupport	NORMAL

	GetIsaSupport	NORMAL

// cache miss: go search the method lists
LCacheMiss:
	// isa still in r11
	MethodTableLookup %a1, %a2	// r11 = IMP
	cmp	%r11, %r11		// set eq (nonstret) for forwarding
	jmp	*%r11			// goto *imp

	END_ENTRY	_objc_msgSend

	
	ENTRY _objc_msgSend_fixup
	int3
	END_ENTRY _objc_msgSend_fixup

	
	STATIC_ENTRY _objc_msgSend_fixedup
	// Load _cmd from the message_ref
	movq	8(%a2), %a2
	jmp	_objc_msgSend
	END_ENTRY _objc_msgSend_fixedup

来分析一下这段汇编代码。

乍一看,如果从LCacheMiss:这里上下分开,可以很明显的看到objc_msgSend就干了两件事情—— CacheLookup 和 MethodTableLookup。



/////////////////////////////////////////////////////////////////////
//
// NilTest return-type
//
// Takes: $0 = NORMAL or FPRET or FP2RET or STRET
//  %a1 or %a2 (STRET) = receiver
//
// On exit:  Loads non-nil receiver in %a1 or %a2 (STRET), or returns zero.
//
/////////////////////////////////////////////////////////////////////

.macro NilTest
.if $0 == SUPER  ||  $0 == SUPER_STRET
 error super dispatch does not test for nil
.endif

.if $0 != STRET
 testq %a1, %a1
.else
 testq %a2, %a2
.endif
 PN
 jz LNilTestSlow_f
.endmacro

NilTest是用来检测是否为nil的。传入参数有4种,NORMAL / FPRET / FP2RET / STRET。

objc_msgSend 传入的参数是NilTest NORMAL
objc_msgSend_fpret 传入的参数是NilTest FPRET
objc_msgSend_fp2ret 传入的参数是NilTest FP2RET
objc_msgSend_stret 传入的参数是NilTest STRET

如果检测方法的接受者是nil,那么系统会自动clean并且return。

GetIsaFast宏可以快速地获取到对象的 isa 指针地址(放到 r11
寄存器,r10会被重写;在 arm 架构上是直接赋值到 r9)



.macro CacheLookup
 
 ldrh r12, [r9, #CACHE_MASK] // r12 = mask
 ldr r9, [r9, #CACHE] // r9 = buckets
.if $0 == STRET  ||  $0 == SUPER_STRET
 and r12, r12, r2  // r12 = index = SEL & mask
.else
 and r12, r12, r1  // r12 = index = SEL & mask
.endif
 add r9, r9, r12, LSL #3 // r9 = bucket = buckets+index*8
 ldr r12, [r9]  // r12 = bucket->sel
2:
.if $0 == STRET  ||  $0 == SUPER_STRET
 teq r12, r2
.else
 teq r12, r1
.endif
 bne 1f
 CacheHit $0
1: 
 cmp r12, #1
 blo LCacheMiss_f  // if (bucket->sel == 0) cache miss
 it eq   // if (bucket->sel == 1) cache wrap
 ldreq r9, [r9, #4]  // bucket->imp is before first bucket
 ldr r12, [r9, #8]!  // r12 = (++bucket)->sel
 b 2b

.endmacro

r12里面存的是方法method,r9里面是cache。r1,r2是SEL。在这个CacheLookup函数中,不断的通过SEL与cache中的bucket->sel进行比较,如果r12 = = 0,则跳转到LCacheMiss_f标记去继续执行。如果r12找到了,r12 = =1,即在cache中找到了相应的SEL,则直接执行该IMP(放在r10中)。

程序跳到LCacheMiss,就说明cache中无缓存,未命中缓存。这个时候就要开始下一阶段MethodTableLookup的查找了。


/////////////////////////////////////////////////////////////////////
//
// MethodTableLookup classRegister, selectorRegister
//
// Takes: $0 = class to search (a1 or a2 or r10 ONLY)
//  $1 = selector to search for (a2 or a3 ONLY)
//   r11 = class to search
//
// On exit: imp in %r11
//
/////////////////////////////////////////////////////////////////////
.macro MethodTableLookup

 MESSENGER_END_SLOW
 
 SaveRegisters

 // _class_lookupMethodAndLoadCache3(receiver, selector, class)

 movq $0, %a1
 movq $1, %a2
 movq %r11, %a3
 call __class_lookupMethodAndLoadCache3

 // IMP is now in %rax
 movq %rax, %r11

 RestoreRegisters

.endmacro

MethodTableLookup 可以算是个接口层宏,主要用于保存环境与准备参数,来调用 __class_lookupMethodAndLoadCache3函数(在objc-class.mm中)。具体是把receiver, selector, class三个参数传给$0,$1,r11,然后再去调用lookupMethodAndLoadCache3方法。最后会将 IMP 返回(从 r11 挪到 rax)。最后在 objc_msgSend中调用 IMP。



/***********************************************************************
* _class_lookupMethodAndLoadCache.
* Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp().
* This lookup avoids optimistic cache scan because the dispatcher 
* already tried that.
**********************************************************************/
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{        
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

__class_lookupMethodAndLoadCache3函数也是个接口层(C编写),此函数提供相应参数配置,实际功能在lookUpImpOrForward函数中。

再来看看lookUpImpOrForward函数实现


IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    Class curClass;
    IMP imp = nil;
    Method meth;
    bool triedResolver = NO;

    /*
    中间是查找过程,详细解析见下。
    */

    // paranoia: look for ignored selectors with non-ignored implementations
    assert(!(ignoreSelector(sel)  &&  imp != (IMP)&_objc_ignored_method));

    // paranoia: never let uncached leak out
    assert(imp != _objc_msgSend_uncached_impcache);

    return imp;
}

接下来一行行的解析。


    runtimeLock.assertUnlocked();

runtimeLock.assertUnlocked(); 这个是加一个读写锁,保证线程安全。


    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

lookUpImpOrForward第5个新参是是否找到cache的布尔量,如果传入的是YES,那么就会调用cache_getImp方法去找到缓存里面的IMP。


/********************************************************************
 * IMP cache_getImp(Class cls, SEL sel)
 *
 * On entry: a1 = class whose cache is to be searched
 *  a2 = selector to search for
 *
 * If found, returns method implementation.
 * If not found, returns NULL.
 ********************************************************************/

 STATIC_ENTRY _cache_getImp

// do lookup
 movq %a1, %r11  // move class to r11 for CacheLookup
 CacheLookup GETIMP  // returns IMP on success

LCacheMiss:
// cache miss, return nil
 xorl %eax, %eax
 ret

LGetImpExit:
 END_ENTRY  _cache_getImp


cache_getImp会把找到的IMP放在r11中。


    if (!cls->isRealized()) {
        rwlock_writer_t lock(runtimeLock);
        realizeClass(cls);
    }

调用realizeClass方法是申请class_rw_t的可读写空间。


    if (initialize  &&  !cls->isInitialized()) {
        _class_initialize (_class_getNonMetaClass(cls, inst));
    }

_class_initialize是类初始化的过程。


retry:
    runtimeLock.read();

runtimeLock.read();这里加了一个读锁。因为在运行时中会动态的添加方法,为了保证线程安全,所以要加锁。从这里开始,下面会出现5处goto done的地方,和一处goto retry。


 done:
    runtimeLock.unlockRead();

在done的地方,会完成IMP的查找,于是可以打开读锁。


    // Ignore GC selectors
    if (ignoreSelector(sel)) {
        imp = _objc_ignored_method;
        cache_fill(cls, sel, imp, inst);
        goto done;
    }

紧接着GC selectors是为了忽略macOS中GC垃圾回收机制用到的方法,iOS则没有这一步。如果忽略,则进行cache_fill,然后跳转到goto done那里去。


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
}


static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
    cacheUpdateLock.assertLocked();

    // Never cache before +initialize is done
    if (!cls->isInitialized()) return;

    // Make sure the entry wasn't added to the cache by some other thread 
    // before we grabbed the cacheUpdateLock.
    if (cache_getImp(cls, sel)) return;

    cache_t *cache = getCache(cls);
    cache_key_t key = getKey(sel);

    // Use the cache as-is if it is less than 3/4 full
    mask_t newOccupied = cache->occupied() + 1;
    mask_t capacity = cache->capacity();
    if (cache->isConstantEmptyCache()) {
        // Cache is read-only. Replace it.
        cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
    }
    else if (newOccupied <= capacity / 4 * 3) {
        // Cache is less than 3/4 full. Use it as-is.
    }
    else {
        // Cache is too full. Expand it.
        cache->expand();
    }
    bucket_t *bucket = cache->find(key, receiver);
    if (bucket->key() == 0) cache->incrementOccupied();
    bucket->set(key, imp);
}

在cache_fill中还会去调用cache_fill_nolock函数,如果缓存中的内容大于容量的 3/4就会扩充缓存,使缓存的大小翻倍。找到第一个空的 bucket_t,以 (SEL, IMP)的形式填充进去。


    // Try this class's cache.

    imp = cache_getImp(cls, sel);
    if (imp) goto done;

如果不忽略,则再次尝试从类的cache中获取IMP,如果获取到,然后也会跳转到goto done去。



    // Try this class's method lists.

    meth = getMethodNoSuper_nolock(cls, sel);
    if (meth) {
        log_and_fill_cache(cls, meth->imp, sel, inst, cls);
        imp = meth->imp;
        goto done;
    }

如果在cache缓存中获取失败,则再去类方法列表里面进行查找。找到后跳转到goto done。


    // Try superclass caches and method lists.

    curClass = cls;
    while ((curClass = curClass->superclass)) {
        // 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;
            }
        }

如果以上尝试都失败了,接下来就会循环尝试父类的缓存和方法列表。一直找到NSObject为止。因为NSObject的superclass为nil,才跳出循环。

如果在父类中找到了该方法method的IMP,接下来就应该把这个方法cache回自己的缓存中。fill完之后跳转goto done语句。


        // Superclass method list.
        meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
            imp = meth->imp;
            goto done;
        }
    }


如果没有在父类的cache中找到IMP,继续在父类的方法列表里面查找。如果找到,跳转goto done语句。


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

这里可以解析一下method的查找过程。在getMethodNoSuper_nolock方法中,会遍历一次methodList链表,从begin一直遍历到end。遍历过程中会调用search_method_list函数。


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


在search_method_list函数中,会去判断当前methodList是否有序,如果有序,会调用findMethodInSortedMethodList方法,这个方法里面的实现是一个二分搜索,具体代码就不贴了。如果非有序,就调用线性的傻瓜式遍历搜索。



    // No implementation found. Try method resolver once.

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

如果父类找到NSObject还没有找到,那么就会开始尝试_class_resolveMethod方法。注意,这些需要打开读锁,因为开发者可能会在这里动态增加方法实现,所以不需要缓存结果。此处虽然锁被打开,可能会出现线程问题,所以在执行完_class_resolveMethod方法之后,会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);
        }
    }
}


这个函数首先判断是否是meta-class类,如果不是元类,就执行_class_resolveInstanceMethod,如果是元类,执行_class_resolveClassMethod。这里有一个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;
}

在这个函数实现中,还会去调用lookUpImpOrForward去查找有没有传入的sel的实现,但是返回值还会返回nil。在imp == _objc_msgForward_impcache会返回nil。_objc_msgForward_impcache是一个标记,这个标记用来表示在父类的缓存中停止继续查找。


IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;

    if (!cls  ||  !sel) return nil;

    imp = lookUpImpOrNil(cls, sel, nil, 
                         YES/*initialize*/, YES/*cache*/, YES/*resolver*/);

    // Translate forwarding function to C-callable external version
    if (!imp) {
        return _objc_msgForward;
    }

    return imp;
}

再回到_class_resolveMethod的实现中,如果lookUpImpOrNil返回nil,就代表在父类中的缓存中找到,于是需要再调用一次_class_resolveInstanceMethod方法。保证给sel添加上了对应的IMP。


    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

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

回到lookUpImpOrForward方法中,如果也没有找到IMP的实现,那么method resolver也没用了,只能进入消息转发阶段。进入这个阶段之前,imp变成_objc_msgForward_impcache。最后再加入缓存中。_objc_msgForward_impcache 第二个作用就是作为方法实现的 imp ,调用转发流程。

三. 消息转发Message Forwarding阶段

到了转发阶段,会调用id _objc_msgForward(id self, SEL _cmd,...)方法。在objc-msg-x86_64.s中有其汇编的实现。


 STATIC_ENTRY __objc_msgForward_impcache
 // Method cache version

 // THIS IS NOT A CALLABLE C FUNCTION
 // Out-of-band condition register is NE for stret, EQ otherwise.

 MESSENGER_START
 nop
 MESSENGER_END_SLOW
 
 jne __objc_msgForward_stret
 jmp __objc_msgForward

 END_ENTRY __objc_msgForward_impcache
 
 
 ENTRY __objc_msgForward
 // Non-stret version

 movq __objc_forward_handler(%rip), %r11
 jmp *%r11

 END_ENTRY __objc_msgForward

在执行_objc_msgForward之后会调用__objc_forward_handler函数。



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

在最新的Objc2.0中会有一个objc_defaultForwardHandler,看源码实现我们可以看到熟悉的语句。当我们给一个对象发送一个没有实现的方法的时候,如果其父类也没有这个方法,则会崩溃,报错信息类似于这样:unrecognized selector sent to instance,然后接着会跳出一些堆栈信息。这些信息就是从这里而来。



void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

#if SUPPORT_STRET
struct stret { int i[100]; };
__attribute__((noreturn)) struct stret 
objc_defaultForwardStretHandler(id self, SEL sel)
{
    objc_defaultForwardHandler(self, sel);
}
void *_objc_forward_stret_handler = (void*)objc_defaultForwardStretHandler;
#endif

#endif

void objc_setForwardHandler(void *fwd, void *fwd_stret)
{
    _objc_forward_handler = fwd;
#if SUPPORT_STRET
    _objc_forward_stret_handler = fwd_stret;
#endif
}

要设置转发只要重写_objc_forward_handler方法即可。在objc_setForwardHandler方法中,可以设置ForwardHandler。

还是回到消息转发上面来。当前的SEL无法找到相应的IMP的时候,开发者可以通过重写- (id)forwardingTargetForSelector:(SEL)aSelector方法来“偷梁换柱”,把消息的接受者换成一个可以处理该消息的对象。



- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if(aSelector == @selector(Method:)){
        return otherObject;
    }
    return [super forwardingTargetForSelector:aSelector];
}

当然也可以替换类方法,那就要重写 + (id)forwardingTargetForSelector:(SEL)aSelector方法,返回值是一个类对象。



+ (id)forwardingTargetForSelector:(SEL)aSelector {
    if(aSelector == @selector(xxx)) {
        return NSClassFromString(@"Class name");
    }
    return [super forwardingTargetForSelector:aSelector];
}

这一步是替消息找备援接收者,如果这一步返回的是nil,那么补救措施就完全的失效了,Runtime系统会向对象发送methodSignatureForSelector:消息,并取到返回的方法签名用于生成NSInvocation对象。为接下来的完整的消息转发生成一个 NSMethodSignature对象。NSMethodSignature 对象会被包装成 NSInvocation 对象,forwardInvocation: 方法里就可以对 NSInvocation 进行处理了。

接下来未识别的方法崩溃之前,系统会做一次完整的消息转发。

我们只需要重写下面这个方法,就可以自定义我们自己的转发逻辑了。


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

实现此方法之后,若发现某调用不应由本类处理,则会调用超类的同名方法。如此,继承体系中的每个类都有机会处理该方法调用的请求,一直到NSObject根类。如果到NSObject也不能处理该条消息,那么就是再无挽救措施了,只能抛出“doesNotRecognizeSelector”异常了。

至此,消息发送和转发的过程都清楚明白了。

 Runtime中的优化

关于Runtime系统中,有3种地方进行了优化。

  • 1.方法列表的缓存
  • 2.虚函数表vTable
  • 3.dyld共享缓存

1.方法列表的缓存

在消息发送过程中,查找IMP的过程,会优先查找缓存。这个缓存会存储最近使用过的方法都缓存起来。这个cache和CPU里面的cache的工作方式有点类似。原理是调用的方法有可能经常会被调用。如果没有这个缓存,直接去类方法的方法链表里面去查找,查询效率实在太低。所以查找IMP会优先搜索饭方法缓存,如果没有找到,接着会在虚函数表中寻找IMP。如果找到了,就会把这个IMP存储到缓存中备用。

基于这个设计,使Runtime系统能能够执行快速高效的方法查询操作。

2.虚函数表

虚函数表也称为分派表,是编程语言中常用的动态绑定支持机制。在OC的Runtime运行时系统库实现了一种自定义的虚函数表分派机制。这个表是专门用来提高性能和灵活性的。这个虚函数表是用来存储IMP类型的数组。每个object-class都有这样一个指向虚函数表的指针。

3.dyld共享缓存

在我们的程序中,一定会有很多自定义类,而这些类中,很多SEL是重名的,比如alloc,init等等。Runtime系统需要为每一个方法给定一个SEL指针,然后为每次调用个各个方法更新元数据,以获取唯一值。这个过程是在应用程序启动的时候完成。为了提高这一部分的执行效率,Runtime会通过dyld共享缓存实现选择器的唯一性。

dyld是一种系统服务,用于定位和加载动态库。它含有共享缓存,能够使多个进程共用这些动态库。dyld共享缓存中含有一个选择器表,从而能使运行时系统能够通过使用缓存访问共享库和自定义类的选择器。




参考文章 :https://www.jianshu.com/p/4d619b097e20
 

猜你喜欢

转载自blog.csdn.net/u014600626/article/details/85338316
今日推荐