前言
IOS底层原理之方法慢速查找流程 中我们对方法的慢速查找流程进行了分析,如果方法最终找不到时,会将imp赋值为forward_imp然后返回,返回后有发生了什么呢,今天来继续探索:
一、找不到方法实现,底层原理
代码实验
上代码:
@interface MHPerson1 : NSObject
-(void)testFunc;
@end
@implementation MHPerson1
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MHPerson1 *person = [MHPerson1 alloc];
[person testFunc];
}
return 0;
}
报错信息如下
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MHPerson1 testFunc]: unrecognized selector sent to instance 0x10060dd20'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff2070587b __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00000001002fbb20 objc_exception_throw + 48
2 CoreFoundation 0x00007fff2078838d -[NSObject(NSObject) __retain_OA] + 0
3 CoreFoundation 0x00007fff2066d90b ___forwarding___ + 1448
4 CoreFoundation 0x00007fff2066d2d8 _CF_forwarding_prep_0 + 120
5 KCObjcBuild 0x0000000100003a20 main + 64
6 libdyld.dylib 0x00007fff205adf5d start + 1
7 ??? 0x0000000000000001 0x0 + 1
)
libc++abi: terminating with uncaught exception of type NSException
查询报错
搜一下,找到了这个,还用 class_isMetaClass(object_getClass(self)) ? ‘+’ : ‘-’,对类方法和实例方法做了判断
__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);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
再搜一下_objc_forward_handler,找到了这个
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
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
__objc_msgForward_impcache,这个似曾相识,之前的IOS底层原理之方法慢速查找流程 中,我们知道查找方法的方法是lookUpImpOrForward,里面有个这个
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
连上了,然后看汇编代码 发现调用了 TailCallFunctionPointer x17这个,搜一下
.macro TailCallFunctionPointer
// $0 = function pointer value
braaz $0
.endmacro
准备工作完成,下面进入动态方法决议
二、动态方法决议
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
// 动态决议方法
return resolveMethod_locked(inst, sel, cls, behavior);
}
enum {
LOOKUP_INITIALIZE = 1,
LOOKUP_RESOLVER = 2,
LOOKUP_NIL = 4,
LOOKUP_NOCACHE = 8,
};
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
第一次时,behavior = 3 、LOOKUP_RESOLVER = 2,3&2 = 2
,所以if表达式成立。
那么接下来执行的方法就是resolveMethod_locked
resolveMethod_locked
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 (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
- 首先判断cls是否是元类
- 如果不是元类只是普通类,那么说明调用的实例方法跳转resolveInstanceMethod流程
- 如果是元类,那么说明调用的是类方法跳转resolveClassMethod流程
- lookUpImpOrForwardTryCache快速查找和慢速查找sel对应的imp 然后返回imp
看一下这个resolveInstanceMethod
resolveInstanceMethod
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// 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 = lookUpImpOrNilTryCache(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 的SEL resolve_sel
- 根据lookUpImpOrNilTryCache (cls, resolve_sel, cls->ISA(true))知道resolveInstanceMethod是类方法,通过快速和慢速查找流程查找resolve_sel对应的imp,缓存resolveInstanceMethod方法
- 直接通过msg(cls, resolve_sel, sel)给类发送消息,从这里也能看到resolveInstanceMethod是类方法
lookUpImpOrNilTryCache(inst, sel, cls)快速和慢速查找流程
- 通过lookUpImpOrNilTryCache来确定resolveInstanceMethod方法中有没有实现sel对应的imp
- 如果实现了,缓存中没有,进入lookUpImpOrForward查找到sel对应imp插入缓存,调用imp查找流程结束
- 如果没有实现,缓存中没有,进入lookUpImpOrForward查找,sel没有查找到对应的imp,此时imp = forward_imp,动态方法决议只调用一次,此时会走done_unlock和done流程,既sel和forward_imp插入缓存,进行消息转发
resolveClassMethod
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
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);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNilTryCache(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 resolveClassMethod:%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));
}
}
}
- resolveClassMethod在NSobject中已经实现,只要元类初始化就可以了,目的是缓存在元类中
- 调用resolveClassMethod类方法,目的是实现可能resolveClassMethod``方法中动态实现sel对应的imp
- imp = lookUpImpOrNilTryCache(inst, sel, cls) 缓存sel对应的imp,不管imp有没有动态添加,如果没有缓存的就是forward_imp
lookUpImpOrNilTryCache方法
lookUpImpOrNilTryCache方法名字,可以理解就是查找imp或者nil尽可能的通过查询cache的方式,在resolveInstanceMethod方法和resolveClassMethod方法都调用lookUpImpOrNilTryCache extern IMP lookUpImpOrNilTryCache(id obj, SEL, Class cls, int behavior = 0);
IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
首先最后一个参数默认是behavior = 0,LOOKUP_NIL = 4, behavior|LOOKUP_NIL 大于等于LOOKUP_NIL ALWAYS_INLINE
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
if (slowpath(!cls->isInitialized())) {
// see comment in lookUpImpOrForward
return lookUpImpOrForward(inst, sel, cls, behavior);
}
IMP imp = cache_getImp(cls, sel);
if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
}
#endif
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
判断cls是否初始化一般都会初始化的缓存中查找
- 在缓存中查找sel对应的imp
- 如果imp存在跳转done流程
- 判断是否有共享缓存给系统底层库用的
- 如果缓存中没有查询到imp,进入慢速查找流程
-
慢速查找流程
- 慢速查找流程中,behavior= 4 ,4 & 2 = 0进入动态方法决议,所以不会一直循环
- 最重要的如果没有查询到此时imp= forward_imp,跳转lookUpImpOrForward中的done_unlock和done流程,插入缓存,返回forward_imp done流程
- done流程: (behavior & LOOKUP_NIL) 且 imp = _objc_msgForward_impcache,如果缓存中的是forward_imp,就直接返回nil,否者返回的imp,LOOKUP_NIL条件就是来查找是否动态添加了imp还有就是将imp插入缓存
lookUpImpOrNilTryCache的主要作用通过LOOKUP_NIL来控制插入缓存,不管sel对应的imp有没有实现,还有就是如果imp返回了有值那么一定是在动态方法决议中动态实现了imp
容错处理
@implementation MHPerson1
- (void)undefinedFun
{
NSLog(@"undefinedFun");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(testFunc)) {
IMP imp = class_getMethodImplementation(self, @selector(undefinedFun));
Method method = class_getInstanceMethod(self, @selector(undefinedFun));
const char *type = method_getTypeEncoding(method);
class_addMethod(self, sel, imp, type);
}
return NO;
}
@end
三、aop和oop
oop
面向对象编程,什么人做什么什么事情,分工非常明确
- 好处:耦合度很低
- 痛点:有很多冗余代码,常规解决办法是提取,那么会有一个公共的类,所有人对公共的类进行集成,那么所有人对公共类进行强依赖,也就代表着出现了强耦合
aop
面向切面编程,是oop的延伸
切点:要切入的方法和切入的类,比如上述的例子中的enjoyLife和FFPerson
优点:对业务无侵入,通过动态方式将某些方法进行注入
缺点:做了一些判断,执行了很多无关代码,包括系统方法,造成性能消耗。会打断apple的方法动态转发流程。
总结:
大部分都是代码,需要一定的耐心来操作验证,希望越来越好