【iOS】消息传递和消息转发机制

news2024/11/25 3:02:51

消息传递机制

在OC语言中,调用对象的方法被叫做消息传递。消息有名称和选择子(selector),可以接受参数,还可能有返回值。

在Objective-C中,如果向某对象传递消息,那就会使用动态绑定机制来决定需要调用的方法。在底层,所有方法都是普通的C语言函数,然而对象收到消息之后,究竟该调用那个方法则完全运行期决定,甚至可以在程序运行时改变,这些特性使得Objective-C成为一门真正的动态语言。

例:OC消息表达式:

id returnValue = [someObject messageName:parameter];

这一段会被编译器处理成:

id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);

someObject称为接受者(receiver),messageName称为“选择子”,选择子和参数一起称为“消息”,编译器看到这条消息会转换成一条标准的 C 语言函数调用。

选择子SEL

OC在编译时会根据方法的名字(包括参数序列),生成一个用来区分这个办法的唯一的一个ID,这个ID就是SEL类型的。我们需要注意的是,只要方法的名字(包括参数序列)相同,那么他们的ID就是相同的。所以不管是父类还是子类,名字相同那么ID就是一样的。

	SEL sell1 = @selector(eat:);
    NSLog(@"sell1:%p", sell1);
    SEL sell2 = @selector(eat);
    NSLog(@"sell2:%p", sell2);
    //sell1:0x100000f63
	//sell2:0x100000f68

这样的机制大大的增加了我们的程序的灵活性,我们可以通过给一个方法传递SEL参数,让这个方法动态的执行某一个方法;我们也可以通过配置文件指定需要执行的方法,程序读取配置文件之后把方法的字符串翻译成为SEL变量然后给相应的对象发送这个消息。

从效率的角度上来说,执行的时候不是通过方法名字而是方法ID也就是一个整数来查找方法,由于整数的查找和匹配比字符串要快得多,所以这样可以在某种程度上提高执行的效率

我们需要注意,@selector等同于是把方法名翻译成SEL方法名,其仅仅关心方法名和参数个数,并不关心返回值与参数类型

生成SEL的过程是固定的,因为它只是一个表明方法的ID,不管是在哪个类写这个dayin方法,SEL值都是固定一个

在Runtime中维护了一个SEL的表,这个表存储SEL不按照类来存储,只要相同的SEL就会被看做一个,并存储到表中。在项目加载时,会将所有方法都加载到这个表中,而动态生成的方法也会被加载到表中。

那么不同的类可以拥有相同的方法,不同类的实例对象执行相同的selector时会在各自的方法列表中去根据SEL去寻找自己类对应的IMP。

IMP本质就是一个函数指针,这个被指向的函数包含一个接收消息的对象id,调用方法的SEL,以及一些方法参数,并返回一个id。因此我们可以通过SEL获得它所对应的IMP,在取得了函数指针之后,也就意味着我们取得了需要执行方法的代码入口,这样我们就可以像普通的C语言函数调用一样使用这个函数指针。

objc_msgSend

我们可以看到转换中,使用到了objc_msgSend 函数,这个函数将消息接收者和方法名作为主要参数,如下所示:

objc_msgSend(receiver, selector)                    // 不带参数
objc_msgSend(receiver, selector, arg1, arg2,...)    // 带参数

objc_msgSend 通过以下几个步骤实现了动态绑定机制:

  • 首先,获取 selector 指向的方法实现。由于相同的方法可能在不同的类中有着不同的实现,因此根据 receiver 所属的类进行判断。
  • 其次,传递 receiver 对象、方法指定的参数来调用方法实现。
  • 最后,返回方法实现的返回值。

消息传递的关键在于objc_class结构体,其有三个关键的字段:

  • isa:指向类的指针。
  • superclass:指向父类的指针。
  • methodLists:类的方法分发表(dispatch table)。

当创建一个新对象时,先为其分配内存,并初始化其成员变量。其中 isa 指针也会被初始化,让对象可以访问类及类的继承链。

下图所示为消息传递过程的示意图:

在这里插入图片描述

  • 当消息传递给一个对象时,首先从运行时系统缓存objc_cache中进行查找。如果找到,则执行。否则,继续执行下面步骤。
  • objc_msgSend通过对象的isa指针获取到类的结构体,然后在方法分发表methodLists中查找方法的selector。如果未找到,将沿着类的superclass找到其父类,并在父类的分发表methodLists中继续查找。
  • 以此类推,一直沿着类的继承链追溯至NSObject类。一旦找到selector,传入相应的参数来执行方法的具体实现,并将该方法加入缓存objc_cache。如果最后仍然没有找到selector,则会进入消息转发流程。

源码解析

快速查找

objc_msgSend在不同架构下都有实现:以arm64为例,代码实现是汇编。为什么选用汇编来实现?速度更快,直接使用参数,免去大量参数的拷贝的开销。在函数和全局变量前面会加下划线“_”,防止符号冲突。

汇编过程:

	//进入objc_msgSend流程
	ENTRY _objc_msgSend
    //流程开始,无需frame
	UNWIND _objc_msgSend, NoFrame

    //判断p0(消息接收者)是否存在,不存在则重新开始执行objc_msgSend
	cmp	p0, #0			// nil check and tagged pointer check
	//如果支持小对象类型,返回小对象或空
#if SUPPORT_TAGGED_POINTERS
    //b是进行跳转,b.le是小于判断,也就是p0小于0的时候跳转到LNilOrTagged
	b.le	LNilOrTagged		//  (MSB tagged pointer looks negative)
#else
    //等于,如果不支持小对象,就跳转至LReturnZero退出
	b.eq	LReturnZero
#endif
    //通过p13取isa
	ldr	p13, [x0]		// p13 = isa
    //通过isa取class并保存到p16寄存器中
	GetClassFromIsa_p16 p13, 1, x0	// p16 = class
  • 首先从cmp p0,#0开始,这里p0是寄存器,存放的是消息接受者。当进入消息发送入口时,先判断消息接收者是否存在,不存在则重新执行objc_msgSend
  • b.le LNilOrTagged,b是跳转到的意思。le是如果p0小于等于0,总体意思是若p0小于等于0,则跳转到LNilOrTagged,执行b.eq LReturnZero直接退出这个函数
  • 如果消息接受者不为nil,汇编继续跑,到CacheLookup NORMAL,在cache中查找imp。

来看一下具体的实现:

//在cache中通过sel查找imp的核心流程
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
	//
	// Restart protocol:
	//
	//   As soon as we're past the LLookupStart\Function label we may have
	//   loaded an invalid cache pointer or mask.
	//
	//   When task_restartable_ranges_synchronize() is called,
	//   (or when a signal hits us) before we're past LLookupEnd\Function,
	//   then our PC will be reset to LLookupRecover\Function which forcefully
	//   jumps to the cache-miss codepath which have the following
	//   requirements:
	//
	//   GETIMP:
	//     The cache-miss is just returning NULL (setting x0 to 0)
	//
	//   NORMAL and LOOKUP:
	//   - x0 contains the receiver
	//   - x1 contains the selector
	//   - x16 contains the isa
	//   - other registers are set as per calling conventions
	//

    //从x16中取出class移到x15中
	mov	x15, x16			// stash the original isa
//开始查找
LLookupStart\Function:
	// p1 = SEL, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
    //ldr表示将一个值存入到p10寄存器中
    //x16表示p16寄存器存储的值,当前是Class
    //#数值 表示一个值,这里的CACHE经过全局搜索发现是2倍的指针地址,也就是16个字节
    //#define CACHE (2 * __SIZEOF_POINTER__)
    //经计算,p10就是cache
	ldr	p10, [x16, #CACHE]				// p10 = mask|buckets
	lsr	p11, p10, #48			// p11 = mask
	and	p10, p10, #0xffffffffffff	// p10 = buckets
	and	w12, w1, w11			// x12 = _cmd & mask
//真机64位看这个
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    //CACHE 16字节,也就是通过isa内存平移获取cache,然后cache的首地址就是 (bucket_t *)
	ldr	p11, [x16, #CACHE]			// p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
//获取buckets
#if __has_feature(ptrauth_calls)
	tbnz	p11, #0, LLookupPreopt\Function
	and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
#else
    //and表示与运算,将与上mask后的buckets值保存到p10寄存器
	and	p10, p11, #0x0000fffffffffffe	// p10 = buckets
    //p11与#0比较,如果p11不存在,就走Function,如果存在走LLookupPreopt
	tbnz	p11, #0, LLookupPreopt\Function
#endif
    //按位右移7个单位,存到p12里面,p0是对象,p1是_cmd
	eor	p12, p1, p1, LSR #7
	and	p12, p12, p11, LSR #48		// x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
	and	p10, p11, #0x0000ffffffffffff	// p10 = buckets
    //LSR表示逻辑向右偏移
    //p11, LSR #48表示cache偏移48位,拿到前16位,也就是得到mask
    //这个是哈希算法,p12存储的就是搜索下标(哈希地址)
    //整句表示_cmd & mask并保存到p12
	and	p12, p1, p11, LSR #48		// x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
	ldr	p11, [x16, #CACHE]				// p11 = mask|buckets
	and	p10, p11, #~0xf			// p10 = buckets
	and	p11, p11, #0xf			// p11 = maskShift
	mov	p12, #0xffff
	lsr	p11, p12, p11			// p11 = mask = 0xffff >> p11
	and	p12, p1, p11			// x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif

    //去除掩码后bucket的内存平移
    //PTRSHIFT经全局搜索发现是3
    //LSL #(1+PTRSHIFT)表示逻辑左移4位,也就是*16
    //通过bucket的首地址进行左平移下标的16倍数并与p12相与得到bucket,并存入到p13中
	add	p13, p10, p12, LSL #(1+PTRSHIFT)
						// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

						// do {
//ldp表示出栈,取出bucket中的imp和sel分别存放到p17和p9
1:	ldp	p17, p9, [x13], #-BUCKET_SIZE	//     {imp, sel} = *bucket--
    //cmp表示比较,对比p9和p1,如果相同就找到了对应的方法,返回对应imp,走CacheHit
	cmp	p9, p1				//     if (sel != _cmd) {
    //b.ne表示如果不相同则跳转到3f
	b.ne	3f				//         scan more
						//     } else {
2:	CacheHit \Mode				// hit:    call or return imp
						//     }
//向前查找下一个bucket,一直循环直到找到对应的方法,循环完都没有找到就调用_objc_msgSend_uncached
3:	cbz	p9, \MissLabelDynamic		//     if (sel == 0) goto Miss;
    //通过p13和p10来判断是否是第一个bucket
	cmp	p13, p10			// } while (bucket >= buckets)
	b.hs	1b

	// wrap-around:
	//   p10 = first bucket
	//   p11 = mask (and maybe other bits on LP64)
	//   p12 = _cmd & mask
	//
	// A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
	// So stop when we circle back to the first probed bucket
	// rather than when hitting the first bucket again.
	//
	// Note that we might probe the initial bucket twice
	// when the first probed slot is the last entry.


#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
	add	p13, p10, w11, UXTW #(1+PTRSHIFT)
						// p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
	add	p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
						// p13 = buckets + (mask << 1+PTRSHIFT)
						// see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
	add	p13, p10, p11, LSL #(1+PTRSHIFT)
						// p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
	add	p12, p10, p12, LSL #(1+PTRSHIFT)
						// p12 = first probed bucket

						// do {
4:	ldp	p17, p9, [x13], #-BUCKET_SIZE	//     {imp, sel} = *bucket--
	cmp	p9, p1				//     if (sel == _cmd)
	b.eq	2b				//         goto hit
	cmp	p9, #0				// } while (sel != 0 &&
	ccmp	p13, p12, #0, ne		//     bucket > first_probed)
	b.hi	4b

LLookupEnd\Function:
LLookupRecover\Function:
	b	\MissLabelDynamic

#if CONFIG_USE_PREOPT_CACHES
#if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16
#error config unsupported
#endif
LLookupPreopt\Function:
#if __has_feature(ptrauth_calls)
	and	p10, p11, #0x007ffffffffffffe	// p10 = buckets
	autdb	x10, x16			// auth as early as possible
#endif

	// x12 = (_cmd - first_shared_cache_sel)
	adrp	x9, _MagicSelRef@PAGE
	ldr	p9, [x9, _MagicSelRef@PAGEOFF]
	sub	p12, p1, p9

	// w9  = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
#if __has_feature(ptrauth_calls)
	// bits 63..60 of x11 are the number of bits in hash_mask
	// bits 59..55 of x11 is hash_shift

	lsr	x17, x11, #55			// w17 = (hash_shift, ...)
	lsr	w9, w12, w17			// >>= shift

	lsr	x17, x11, #60			// w17 = mask_bits
	mov	x11, #0x7fff
	lsr	x11, x11, x17			// p11 = mask (0x7fff >> mask_bits)
	and	x9, x9, x11			// &= mask
#else
	// bits 63..53 of x11 is hash_mask
	// bits 52..48 of x11 is hash_shift
	lsr	x17, x11, #48			// w17 = (hash_shift, hash_mask)
	lsr	w9, w12, w17			// >>= shift
	and	x9, x9, x11, LSR #53		// &=  mask
#endif

	// sel_offs is 26 bits because it needs to address a 64 MB buffer (~ 20 MB as of writing)
	// keep the remaining 38 bits for the IMP offset, which may need to reach
	// across the shared cache. This offset needs to be shifted << 2. We did this
	// to give it even more reach, given the alignment of source (the class data)
	// and destination (the IMP)
	ldr	x17, [x10, x9, LSL #3]		// x17 == (sel_offs << 38) | imp_offs
	cmp	x12, x17, LSR #38

.if \Mode == GETIMP
	b.ne	\MissLabelConstant		// cache miss
	sbfiz x17, x17, #2, #38         // imp_offs = combined_imp_and_sel[0..37] << 2
	sub	x0, x16, x17        		// imp = isa - imp_offs
	SignAsImp x0
	ret
.else
	b.ne	5f				        // cache miss
	sbfiz x17, x17, #2, #38         // imp_offs = combined_imp_and_sel[0..37] << 2
	sub x17, x16, x17               // imp = isa - imp_offs
.if \Mode == NORMAL
	br	x17
.elseif \Mode == LOOKUP
	orr x16, x16, #3 // for instrumentation, note that we hit a constant cache
	SignAsImp x17
	ret
.else
.abort  unhandled mode \Mode
.endif

5:	ldursw	x9, [x10, #-8]			// offset -8 is the fallback offset
	add	x16, x16, x9			// compute the fallback isa
	b	LLookupStart\Function		// lookup again with a new isa
.endif
#endif // CONFIG_USE_PREOPT_CACHES

.endmacro

1. 流程:

  1. 获取到指向 cache 和 _bucketsAndMaybeMask;
  2. 从 _bucketsAndMaybeMask 中分别取出 buckets 和 mask,并由 mask 根据哈希算法计算出哈希下标;
  3. 根据所得的哈希下标 begin 和 buckets 首地址,取出哈希下标对应的 bucket;
  4. 进入 do-while 循环,根据 bucket 中的 sel 查找;

通过内存平移获得cache 和 _bucketsAndMaybeMask, _bucketsAndMaybeMask中高16位存mask,低48位存buckets(高16位 | 低48位 = mask | buckets), 即_bucketsAndMaybeMask = mask(高位16) + buckets指针(低48位)。

将objc_msgSend的参数p1(即第二个参数_sel)& mask,通过哈希算法,得到需要查找存储sel-imp的bucket下标begin,即p12 = begin = _sel & mask,因为在存储sel-imp时,也是通过同样哈希算法计算哈希下标进行存储。

static inline mask_t cache_hash(SEL sel, mask_t mask) 
{
    return (mask_t)(uintptr_t)sel & mask;
}

根据计算的哈希下标begin 乘以单个bucket占用的内存大小,得到buckets首地址距离begin下标指向的bucket在实际内存中的偏移量。通过首地址 + 实际偏移量,获取哈希下标begin对应的bucket。bucket是有sel和imp两个属性组成,每个属性都是8个字节的大小,所以bucket的大小是16

在do-while循环中:

  • 第一次do-while循环,从begin —> 0 查找一遍,如果没命中,p9不为nil,开始第二次do-while循环;
  • 第二次do-while循环,从mask —> 0再次查找一遍;
  • 依然如此,则执行__objc_msgSend_uncached —> MethodTableLookup —> _lookUpImpOrForward开始查找方法列表。

在缓存中找到了方法那就直接调用,找到sel就会进入CacheHit,去return or call imp:返回或调用方法的实现(imp)。

2. CacheHit的内容:上图的Mode代表走下面的NORMAL流程,authenticate and call imp意思验证并调用方法实现。

#define NORMAL 0
#define GETIMP 1
#define LOOKUP 2
// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro CacheHit
.if $0 == NORMAL
	TailCallCachedImp x17, x10, x1, x16	// authenticate and call imp//调用imp
.elseif $0 == GETIMP
	mov	p0, p17
	cbz	p0, 9f			// don't ptrauth a nil imp
	AuthAndResignAsIMP x0, x10, x1, x16	// authenticate imp and re-sign as IMP
9:	ret				// return IMP//返回imp
.elseif $0 == LOOKUP// 执行__objc_msgSend_uncached,开始方法列表查找
	// No nil check for ptrauth: the caller would crash anyway when they
	// jump to a nil IMP. We don't care if that jump also fails ptrauth.
	AuthAndResignAsIMP x17, x10, x1, x16	// authenticate imp and re-sign as IMP
	cmp	x16, x15
	cinc	x16, x16, ne			// x16 += 1 when x15 != x16 (for instrumentation ; fallback to the parent class)
	ret				// return imp via x17
.else
.abort oops
.endif
.endmacro

3. 缓存中没有找到方法

如果没有找到缓存,查找下一个bucket,一直循环直到找到对应的方法,循环完都没有找到就调用__objc_msgSend_uncached

下面是上述判断跳转代码:

//LGetIsaDone是一个入口
LGetIsaDone:
	// calls imp or objc_msgSend_uncached
    //进入到缓存查找或者没有缓存查找方法的流程
	CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached

__objc_msgSend_uncached源码汇编:

	STATIC_ENTRY __objc_msgSend_uncached
	UNWIND __objc_msgSend_uncached, FrameWithNoSaves

	// THIS IS NOT A CALLABLE C FUNCTION
	// Out-of-band p15 is the class to search
	
	MethodTableLookup
	TailCallFunctionPointer x17

	END_ENTRY __objc_msgSend_uncached

其中调用了MethodTableLookup宏: 从方法列表中去查找方法。

看一下它的结构:

	.macro MethodTableLookup
	
	SAVE_REGS MSGSEND

	// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
	// receiver and selector already in x0 and x1
	mov	x2, x16
	mov	x3, #3
	bl	_lookUpImpOrForward

	// IMP in x0
	mov	x17, x0

	RESTORE_REGS MSGSEND

.endmacro

其中bl表示调用了方法_lookUpImpOrForward,_lookUpImpOrForward在汇编里找不到,因为汇编的函数比C++的多一个下划线,需要去掉下划线,去找到lookUpImpOrForward方法实现

至此快速查找imp汇编部分就结束了,接下来到了慢速查找过程:c/c++环节。

总结消息发送快速查找imp(汇编):

objc_msgSend(receiver, sel, …)

  1. 检查消息接收者receiver是否存在,为nil则不做任何处理
  2. 通过receiver的isa指针找到对应的class类对象
  3. 找到class类对象进行内存平移,找到cache
  4. 从cache中获取buckets
  5. 从buckets中对比参数sel,看在缓存里有没有同名方法
  6. 如果buckets中有对应的sel --> cacheHit --> 调用imp
  7. 如果buckets中没有对应的sel --> _objc_msgSend_uncached -> _lookUpImpOrForward (c/c++慢速查找)

慢速查找

方法缓冲

苹果认为如果一个方法被调用了,那个这个方法有更大的几率被再此调用,既然如此直接维护一个缓存列表,把调用过的方法加载到缓存列表中,再次调用该方法时,先去缓存列表中去查找,如果找不到再去方法列表查询。这样避免了每次调用方法都要去方法列表去查询,大大的提高了速率。

查找过程

先看lookUpImpOrForward函数的实现:

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

    if (slowpath(!cls->isInitialized())) {
        // The first message sent to a class is often +new or +alloc, or +self
        // which goes through objc_opt_* or various optimized entry points.
        //
        // However, the class isn't realized/initialized yet at this point,
        // and the optimized entry points fall down through objc_msgSend,
        // which ends up here.
        //
        // We really want to avoid caching these, as it can cause IMP caches
        // to be made with a single entry forever.
        //
        // Note that this check is racy as several threads might try to
        // message a given class for the first time at the same time,
        // in which case we might cache anyway.
        behavior |= LOOKUP_NOCACHE;
    }

    // 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.
    // 检查当前类是个已知类
    checkIsKnownClass(cls);
    // 确定当前类的继承关系
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE); 
    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();
    curClass = cls;

    // The code used to lookup 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();;) {
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
            // 如果是常量优化缓存
            // 再一次从cache查找imp
            // 目的:防止多线程操作时,刚好调用函数,此时缓存进来了
#if CONFIG_USE_PREOPT_CACHES // iOS操作系统且真机的情况下
            imp = cache_getImp(curClass, sel); //cache中找IMP
            if (imp) goto done_unlock; //找到就直接返回了
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else { //如果不是常量优化缓存
            // 当前类的方法列表。
            method_t *meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }
            // 每次判断都会把curClass的父类赋值给curClass
            if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                // No implementation found, and method resolver didn't help.
                // Use forwarding.
                imp = forward_imp;
                break;
            }
        }

        // 如果超类链中存在循环,则停止。
        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)) {
            // 在超类中找到方法。在这个类中缓存它。
            goto done;
        }
    }

    // 没有实现,尝试一次方法解析器。
	// 这里就是消息转发机制第一层的入口
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES // iOS操作系统且真机的情况下
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        log_and_fill_cache(cls, imp, sel, inst, curClass);
    }
 done_unlock:
    runtimeLock.unlock();
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

-未完

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/608919.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++进阶 —— 范围for(C++11新特性)

目录 一&#xff0c;范围for介绍 二&#xff0c;范围for注意事项 一&#xff0c;范围for介绍 范围for&#xff08;range-based for loop&#xff09;是C11新引入的特性&#xff0c;可遍历各种序列结构的容器&#xff08;如数组、vector、list等&#xff09;&#xff1b;每次循…

【QT】Qt ApplicationManager Compositor源码分析

Qt ApplicationManager的Compositor功能分析 根据Qt ApplicationManager官网介绍&#xff0c;它基于Wayland协议实现了Compositor功能。下述为官网介绍。实际上&#xff0c;QtApplicationManager是使用了QtWayland模块来实现Compositor的。Wayland是一套旨在替代XWindow的 Com…

微机实验:第5章——存储器设计

存储器设计 将两片6116所有的存储单元都写入11H。 提示&#xff1a;6116的存储容量为2K*8b&#xff0c;片内地址为0000H-07FFH,两片一起构成F8000H-F8FFFH的内存空间。 仿真调试时可以看到&#xff1a;每片从0000H-07FFH的每个存储单元均显示11H。 CODE SEGMENTASSUME CS:C…

4-4 哈夫曼编码

博主简介&#xff1a;一个爱打游戏的计算机专业学生博主主页&#xff1a; 夏驰和徐策所属专栏&#xff1a;算法设计与分析 1.什么是哈夫曼编码&#xff1f; 哈夫曼编码&#xff08;Huffman coding&#xff09;是一种用于数据压缩的无损编码方法。它是由David A. Huffman在1952…

STM32F4_软件模拟SPI

目录 1. 硬件连接 2. SPI通讯协议 3. W25Q64 简介 4. 程序详解 4.1 main.c 4.2 SPI.c 4.3 SPI.h 4.4 W25Q128.c 4.5 W25Q128.h 4.6 OLED.c 4.7 OLED.h 4.8 OLED_Font.h 5. 实验结果 我们都知道&#xff0c;SPI 和 IIC 一样&#xff0c;都可以通过硬件方式和软件方…

JSON基础(待补充)

一、JSON初识 1.1基础认识 JSON是一种轻量级的数据交换格式&#xff0c;它基于JavaScript语言的对象表示法&#xff0c;可以在多种语言之间进行数据交换。JSON的基本数据类型有数值、字符串、布尔值、数组、对象和空值。JSON的格式简洁易读&#xff0c;也易于解析和处理。JSON…

【数据结构】由完全二叉树引申出的堆的实现

【数据结构】由完全二叉树引申出的堆的实现 一、什么是堆二、目标三、实现1、初始化工作2、堆的插入(堆的创建)2.1、向上调整建堆2.1.1、向上调整算法原理解析2.1.2、代码实现 2.2、向下调整建堆2.2.1、向下调整算法原理解析2.2.2、代码实现 2.3、“向上”和“向下”复杂度的差…

初识网络安全

目录 HTML前置基础知识 1、id和class区别&#xff1a; 2、一些常用的属性&#xff1a; 3、HTML字符编码和实体编码 4、URL介绍 网址的组成部分&#xff1a; TTL值 DNS工作原理和资源记录及其种类&#xff1a; 5、正确区分“加密”和“签名” 6、状态码 1xx &#xf…

如何安装pycharm

PyCharm是JetBrains公司推出的一款Python集成开发环境&#xff08;IDE&#xff09;&#xff0c;可以提供高效的Python代码编写、调试和测试。以下是一些PyCharm的主要功能&#xff1a; 代码智能提示和自动补全功能&#xff1b;支持调试和测试Python代码&#xff1b;完整的Pyth…

基于Springboot+Vue的幼儿园管理系统设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架下…

汽车相关知识及术语

1 汽车构造与制造流程 1.1 汽车构造 汽车可以分为四大部分 车身&#xff1a; 骨架、车身钣金件以及座椅、仪表、天窗、车外后视镜等车身附件 动力系统&#xff1a; 发动机和变速器 底盘&#xff1a; 传动系统、悬架系统、转向系统、制动系统和车轮轮胎 电气电子系统&#…

《Apollo 智能驾驶进阶课程》三、无人车自定位技术

1. 什么是无人车自定位系统 相对一个坐标系来确定无人车的位置和姿态 定位的指标要求大概分为三个部分&#xff1a;精度、鲁棒性、场景 定位精度必须控制在10厘米以内&#xff0c;才能使行驶中的自动驾驶车辆避免出现碰撞/车道偏离的情况。鲁棒性一般情况下用最大值来衡量。…

Java IO流详细教程

目录 一、IO介绍 IO流体系 字节流 字节输出流&#xff1a;FileoutputStream 字节输入流FilelnputStream 字符流 字符输入流 字符输出流 缓冲流 字节缓冲流 字符缓冲流 序列化、反序列化流 序列化/对象操作输出流 反序列化/对象操作输入流 打印流 字节打印流 字…

firewalld与iptables练习

1、禁止一个IP访问 iptables -I INPUT -s $ip -j REJECT 2、清空默认的防火墙默认规则&#xff1a; iptables -F 3、保存清空后的防火墙规则表 service iptables save 4、firewall-cmd --list-all #查看防火墙规则&#xff08;只显示/etc/firewalld/zones/public.xml中防火墙…

投票活动链接创建微信链接视频投票线上免费投票链接

近些年来&#xff0c;第三方的微信投票制作平台如雨后春笋般络绎不绝。随着手机的互联网的发展及微信开放平台各项基于手机能力的开放&#xff0c;更多人选择微信投票小程序平台&#xff0c;因为它有非常大的优势。 1.它比起微信公众号自带的投票系统、传统的H5投票系统有可以图…

从零手写操作系统之RVOS协作式多任务切换实现-03

从零手写操作系统之RVOS协作式多任务切换实现-03 任务&#xff08;task&#xff09;多任务 &#xff08;Multitask&#xff09;任务上下文&#xff08;Context&#xff09;多任务系统的分类协作式多任务 创建和初始化第 1 号任务切换到第一号任务执行协作式多任务 - 调度初始化…

字典树算法(C/C++)

目录 一、字典树算法的概念介绍 二、字典树算法的实现 三、例题 &#xff08;注&#xff1a;借鉴蓝桥杯国赛特训营&#xff09; 一、字典树算法的概念介绍 首先我们看下字典的组织方式 Trie 的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效…

实训总结-----Scrapy爬虫

1.安装指令 pip install scrapy 2.创建 scrapy 项目 任意终端 进入到目录(用于存储我们的项目) scrapy startproject 项目名 会在目录下面 创建一个以 项目名 命名的文件夹 终端也会有提示 cd 项目名 scrapy genspider example example.com 3.运行爬虫指令 scrapy craw…

ffmpeg之AVFormatContext结构体详细解释

AVFormatContext 作用 AVFormatContext主要起到了管理和存储媒体文件相关信息的作用。它是一个比较重要的结构体&#xff0c;在FFmpeg中用于表示媒体文件的格式上下文&#xff0c;其中包含了已经打开的媒体文件的详细信息&#xff0c;包括媒体文件的格式、媒体流的信息、各个媒…

【笔记】使用电脑连接树莓派 并在电脑屏幕上显示树莓派桌面(无需额外为树莓派购买显示器)

一、前言 想在树莓派上跑 yolo5&#xff0c;为了方便地看到代码的检测结果&#xff0c;需要为树莓派外接显示器&#xff0c;但是手头并没有额外的显示器&#xff0c;于是想在电脑屏幕上显示树莓派的桌面&#xff0c;对解决的过程作一些记录。 二、基本流程 树莓派系统的烧录…