经典案例
- 定义一个方法, 本身类和父类都不去实现,就会爆出经典错误
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_impdone流程 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方法,和上面探究的吻合。 动态方法决议优点
- 可以统一处理方法崩溃的问题,出现方法崩溃可以上报服务器,或者跳转到首页
- 如果项目中是不同的模块你可以根据命名不同,进行业务的区别
- 这种方式叫切面编程熟成
AOPAOP和OOP的区别 OOP:实际上是对对象的属性和行为的封装,功能相同的抽取出来单独封装,强依赖性,高耦合AOP:是处理某个步骤和阶段的,从中进行切面的提取,有重复的操作行为,AOP就可以提取出来,运用动态代理,实现程序功能的统一维护,依赖性小,耦合度小,单独把AOP提取出来的功能移除也不会对主代码造成影响。AOP更像一个三维的纵轴,平面内的各个类有共同逻辑的通过AOP串联起来,本身平面内的各个类没有任何的关联