ios 底层 方法调用 快速查找

1,000 阅读2分钟

前言

我们知道,函数缓存到cache_t 提高了方法调用的访问速度。那方法调用的过程是怎么样呢

方法调用入口

我先先看下方法的调用接口, 我们在main函数中写个普通的调用接口

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        [person sayNB];
      
    }
    return 0;
}

然后在该目录下,使用clang命令编译下:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

编译完成后生成代码:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));

    }
    return 0;
}

分析

在生成的cpp文件中,发现方法的调用基本最终是调用接口objc_msgSend,而sel_registerName是通过字符串生成SEL的接口。

SEL,方法的目录。

IMP, 方法的实现地址

objc_msgSend 的实现流程

通过代码查找,我们发现objc_msgSend底层使用汇编代码实现的,为什么用汇编呢

  • 提高代码访问速度。

  • 一些未知参数的识别,用C或者C++来实现比较困难

汇编1

我们在进入CacheLookup查看方法获取流程

cacheLookup
CacheHit即为把响应的IMP返回给接受者

CheckMiss说明类的缓存中没有响应的IMP,会调用**__objc_msgSend_uncached**进行下一步查找,我们在进入源码看下

	STATIC_ENTRY __objc_msgSend_uncached
	UNWIND __objc_msgSend_uncached, FrameWithNoSaves

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

	END_ENTRY __objc_msgSend_uncached

发现这里主要调用了MethodTableLookup,方法表查询,在进入源码,看看实现

.macro MethodTableLookup
	
	// push frame
	SignLR
	stp	fp, lr, [sp, #-16]!
	mov	fp, sp

	// save parameter registers: x0..x8, q0..q7
	sub	sp, sp, #(10*8 + 8*16)
	stp	q0, q1, [sp, #(0*16)]
	stp	q2, q3, [sp, #(2*16)]
	stp	q4, q5, [sp, #(4*16)]
	stp	q6, q7, [sp, #(6*16)]
	stp	x0, x1, [sp, #(8*16+0*8)]
	stp	x2, x3, [sp, #(8*16+2*8)]
	stp	x4, x5, [sp, #(8*16+4*8)]
	stp	x6, x7, [sp, #(8*16+6*8)]
	str	x8,     [sp, #(8*16+8*8)]

	// receiver and selector already in x0 and x1
	mov	x2, x16
	bl	__class_lookupMethodAndLoadCache3

	// IMP in x0
	mov	x17, x0

看出来,这部分主要进行了参数的处理,以及调用了class_lookupMethodAndLoadCache3方法的调用,而看代码发现从这里开始就是c/c++函数了,就是慢速查找流程了。

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{        
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

总结

  • ios 方法调用都是通过objc_msgSend进行的
  • objc_msgSend首先会走快速查找流程
  • 通过找到类的cache_t,通过哈希算法拿到key,再通过哈希表找到对象的imp
  • 如果没有找到,就调用慢速查找流程