经典案例
- 定义一个方法, 本身类和父类都不去实现,就会爆出经典错误
unrecognized selector sent to instance xxx
,在msgSend底层(二)中,当父类为nil
时,会进行一个赋值imp = forward_imp
,再来看看
forward_imp
=_objc_msgForward_impcache
,源码查看下_objc_msgForward_impcache
的底层实现,全局搜索_objc_msgForward_impcache
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward //跳转 __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
...
.macro TailCallFunctionPointer
// $0 = function pointer value
br $0 //跳转 imp
.endmacro
...
-
__objc_msgForward_impcache
底层是汇编实现,主要代码b __objc_msgForward
-
__objc_msgForward
中TailCallFunctionPointer
是个宏,前面探究过就是跳转imp
。x17
寄存器存放的是imp
,从汇编中可以看出跟x17
有关系的就是__objc_forward_handler
-
全局搜索
__objc_forward_handler
汇编中没有具体的实现,那就不在汇编中,可能在C/C++
源码中,全局搜索objc_forward_handler
,源码如下
动态方法决议
- 慢速查找流程
lookUpImpOrForward
中,如果没有查找到imp
就会走动态方法决议流程resolveMethod_locked
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior){
...
// No implementation found. Try method resolver once.
// 如果查询方法没有实现,系统会尝试一次方法解析
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
//动态方法决议
return resolveMethod_locked(inst, sel, cls, behavior);
}
...
}
查看resolveMethod_locked
源码
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
// inst 类 //cls元类
//查询元类有没有实现 NSObject默认实现resolveClassMethod方法
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);
//类方法相当于元类中的实例方法,同样去快速和慢速的查找
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
...
}
else {
// Method resolver didn't add anything?
...
}
}
}
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)
{
// LOOKUP_NIL = 4 没有传参数behavior = 0 0 | 4 = 4
return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
//cls 是否初始化
if (slowpath(!cls->isInitialized())) {
// 没有初始化就去查找 lookUpImpOrForward 查找时可以初始化
return lookUpImpOrForward(inst, sel, cls, behavior);
}
//在缓存中查找sel对应的imp
IMP imp = cache_getImp(cls, sel);
// imp有值 进入done流程
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
// 缓存中没有查询到imp 进入慢速查找流程
// behavior = 4 ,4 & 2 = 0 不会进入动态方法决议,所以不会一直循环
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
//(behavior & LOOKUP_NIL) = 4 & 4 = 1
//LOOKUP_NIL 只是配合_objc_msgForward_impcache 写入缓存
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
resolveInstanceMethod
代码探究
@interface ZMPerson : NSObject
- (void)sayHello;
+ (void)test;
@end
@implementation ZMPerson
+ (BOOL)resolveClassMethod:(SEL)sel {
NSLog(@"--进入%@--",NSStringFromSelector(sel));
if (@selector(test) == sel) {
IMP imp = class_getMethodImplementation(object_getClass([self class]), @selector(test2));
Method mth = class_getClassMethod(object_getClass([self className]), @selector(test2));
const char *type = method_getTypeEncoding(mth);
return class_addMethod(object_getClass([self class]), sel, imp, type);
}
return [super resolveClassMethod:sel];
}
+ (void)test2{
NSLog(@"--%s---",__func__);
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"--进入%@--",NSStringFromSelector(sel));
if (@selector(sayHello) == sel) {
IMP imp = class_getMethodImplementation(self , @selector(sayHello2));
Method meth = class_getInstanceMethod(self , @selector(sayHello2));
const char * type = method_getTypeEncoding(meth);
return class_addMethod(self ,sel, imp, type);;
}
return [super resolveInstanceMethod:sel];
}
- (void)sayHello2{
NSLog(@"--%s---",__func__);
}
@end
- 整合动态方法决议
msg(cls, resolve_sel, sel)
也可以验证objc_msgSend
发送消息不区分-
和+
方法。objc_msgSend
的接收者cls
是元类
,意味着像元类
中发消息,消息查找会到根元类
去查找,所以resolveInstanceMethod
在元类
中,才会被调用,所以在类中的resolveInstanceMethod
方法不会被调用,不是说元类
和类
的名字是一样的嘛,但是地址不一样哦
创建一个NSObject + ZM
,在分类中添加resolveInstanceMethod
方法,因为根元类
的父类是根类
,根元类
找不到会到根类
中查找,因为根元类
没法创建所以只能用根类
#import "NSObject+ZM.h"
#import <objc/runtime.h>
@implementation NSObject (ZM)
+(BOOL)resolveInstanceMethod:(SEL)sel{
if (@selector(sayHello) == sel) {
NSLog(@"--进入%@--",NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(self , @selector(sayHello2));
Method meth = class_getInstanceMethod(self , @selector(sayHello2));
const char * type = method_getTypeEncoding(meth);
return class_addMethod(self ,sel, imp, type);;
}else if (@selector(test) == sel){
NSLog(@"--进入%@--",NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(object_getClass([self class]), @selector(newTest));
Method meth = class_getClassMethod(object_getClass([self class]) , @selector(newTest));
const char * type = method_getTypeEncoding(meth);
return class_addMethod(object_getClass([self class]) ,sel, imp, type);;
}
return NO;
}
- (void)sayHello2{
NSLog(@"--%s---",__func__);
}
+(void)newTest{
NSLog(@"--%s---",__func__);
}
@end
实例方法是类方法调用,系统都自动调用了resolveInstanceMethod
方法,和上面探究的吻合。 动态方法决议优点
- 可以统一处理方法崩溃的问题,出现方法崩溃可以上报服务器,或者跳转到首页
- 如果项目中是不同的模块你可以根据命名不同,进行业务的区别
- 这种方式叫切面编程熟成
AOP
AOP
和OOP
的区别 OOP
:实际上是对对象的属性和行为的封装,功能相同的抽取出来单独封装,强依赖性,高耦合AOP
:是处理某个步骤和阶段的,从中进行切面的提取,有重复的操作行为,AOP就可以提取出来,运用动态代理,实现程序功能的统一维护,依赖性小,耦合度小,单独把AOP
提取出来的功能移除也不会对主代码造成影响。AOP
更像一个三维的纵轴,平面内的各个类有共同逻辑的通过AOP
串联起来,本身平面内的各个类没有任何的关联