基础
- 编译时:编译器将源代码翻译成机器能识别的代码(或某个中间状态的语言)。包含词法分析、语言分析等。
- 运行时:将磁盘中的代被装载到内存中执行。运行时的类型检查与**编译时的类型检查(静态类型检查)**不同,不是简单的扫描代码,而是在内存中做些操作与判断。
- 方法的本质:消息
- 消息接收者
- 消息主体
流程探索
首先我们有这样的代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *p = [LGPerson alloc] ;
[p saySomething];
}
return 0;
}
在调用saySomething方法时打上断点并开启Always Show Diassembly可以看到
实际底层调用了
objc_msgSend
快速查找(汇编)
我们以arm64架构的汇编代码为例
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
// 1. cmp 消息接收者是否存在
cmp p0, #0
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
// 2. 获取消息接收者的class信息(`cache_getImp`存在class中)
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
LGetIsaDone:
// 3. 利用class中的cache进行查找
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
···
END_ENTRY _objc_msgSend
由上面的流程我们可以知道,如果在缓存中没有找到该方法,则会调用CacheLookup传入的第二个参数(方法)__objc_msgSend_uncached
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// 1. 调用_lookUpImpOrForward查找方法列表
MethodTableLookup // bl _lookUpImpOrForward
// 2. 调用方法
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
流程图:
慢速查找(C/C++)
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) {
// 汇编层 方法查找
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
···
// 准备1. 判断当前class是否已经注册到缓存表中(即注册类)
checkIsKnownClass(cls);
// 准备2. 类、元类、父类按需递归初始化(便于方法查找)
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
···
curClass = cls;
// 核心!
for (unsigned attempts = unreasonableClassCount();;) {
if (curClass->cache.isConstantOptimizedCache(true)) {
#if CONFIG_USE_PREOPT_CACHES
// 1. 若缓存(汇编层级)中查找到imp,返回imp
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// 2. 在本类的方法列表中通过sel查找
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp(false);
goto done;
}
// 3. 若无,递归查找父类
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
imp = forward_imp;
break;
}
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
break;
}
if (fastpath(imp)) {
goto done;
}
}
// 4. 若递归查找完父类也没找到的话,走到动态方法决议
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);
}
done_unlock:
··· // 返回imp
}
其他重点代码
除了上面的基本流程外,我还想额外探究一下最开始调用的_objc_msgForward_impcache方法(以arm64架构为例子)
STATIC_ENTRY __objc_msgForward_impcache
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
我们会发现,直接全局搜__objc_forward_handler是搜不到的,可见这不是汇编方法,而是C/C++方法,因此去掉一个下划线再搜索就可以查到
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
// objc_defaultForwardHandler
__attribute__ ((noreturn, cold)) 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**);
}
也就是说如果方法找不到,就会有我们经典的unrecognized selector崩溃
动态方法决议
准备
在慢速查找流程中,如果找不到,会走到resolveMethod_locked,在调用时传入的behavior为LOOKUP_INITIALIZE | LOOKUP_RESOLVER,由如下可知
enum {
LOOKUP_INITIALIZE = 1,
LOOKUP_RESOLVER = 2,
LOOKUP_NIL = 4,
LOOKUP_NOCACHE = 8,
};
计算值为2,再执行behavior ^= LOOKUP_RESOLVER;可知behavior从现在开始为0,也就是说resolveMethod_locked方法只会调用一遍
流程
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
···
if (! cls->isMetaClass()) { // 1. 若不是元类型(即为实例对象),调用resolveInstanceMethod
resolveInstanceMethod(inst, sel, cls);
} else {
// 2. 若是元类型(Class/MetaClass),调用resolveClassMethod
resolveClassMethod(inst, sel, cls);
// 3. 若此时缓存中仍找不到该类方法,则会调用resolveInstanceMethod
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
对象方法的动态决议
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
···
SEL resolve_sel = @selector(resolveInstanceMethod:);
// 由于NSObject中有默认实现,所以这里不会return
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(true))) {
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
// 1. 系统通过objc_msgSend调用resolveInstanceMethod:方法
bool resolved = msg(cls, resolve_sel, sel);
// 2. 调用完resolveInstanceMethod:方法后,会再查找一次
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform(···);
} else {
_objc_inform(···);
}
}
}
也就是说,我们在自实现resolveInstanceMethod方法时,需要将方法添加到类信息中,下面是一个小示例
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"resolveInstanceMethod!!!!");
if (sel == @selector(testFunc)) {
IMP imp = class_getMethodImplementation(self, @selector(testFunc2));
Method method = class_getInstanceMethod(**self**, **@selector**(testFunc2));
const char type = method_getTypeEncoding(method);
return class_addMethod(**self**,sel,imp,type);
} else {
return [super resolveInstanceMethod:sel];
}
}
类方法的动态决议
static void resolveClassMethod(**id** inst, **SEL** sel, Class cls) {
···
// 由于NSObject中有默认实现,所以这里不会return
if (!lookUpImpOrNilTryCache(inst, **@selector**(resolveClassMethod:), cls)) {
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform(···);
} else {
_objc_inform(···);
}
}
}
也就是说,我们在自实现resolveClassMethod方法时,需要将方法添加到类信息中
同时我们可以看到,在进行类方法的动态方法决议时,也会再调用一次resolveInstanceMethod
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
所以我们其实在自实现的resolveInstanceMethod中,可以进行对象方法和类方法的处理。
消息转发
动态方法决议这一步如果失败的话,难道就真的没有补救办法了吗?我们可以通过以下方法进行探索
extern void instrumentObjcMessageSends(BOOL flag);
// 使用示例
Demo *demo = [Demo alloc];
instrumentObjcMessageSends(YES);
[demo testFunc]; // 该方法未实现
instrumentObjcMessageSends(NO);
该方法会在根目录的tmp文件夹下生成一个msgSends-xxxx文件
快速转发
点击进去查看方法的调用流程,可以看到在resolveInstanceMethod之后首先调用了forwardingTargetForSelector方法
我们来看看苹果官方文档对该方法的解释:
相当于是重定向,返回一个其他对象来接收方法调用的消息。
慢速转发
若没有实现forwardingTargetForSelector方法或返回的对象也没有实现方法的话,则会走到methodSignatureForSelector
也就是说,我们可以通过实现
methodSignatureForSelector,返回一个方法签名。那么谁来解读该方法呢?还是刚刚那个文件,我们可以看到- Demo NSObject forwardInvocation:,也就是说可以通过实现forwardInvocation来解读。所以这两个方法必须成对出现。
- (void)forwardInvocation:(NSInvocation *)invocation {
[self doesNotRecognizeSelector:(invocation ? [invocation selector] : 0)];
}
- (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("-[%s %s]: unrecognized selector sent to instance %p",
object_getClassName(self), sel_getName(sel), self);
}
会有我们经典的unrecognized selector崩溃