通过前面的研究知道了, 方法的过程实际上就是 objc_msgSend
进行消息查找的过程。在进行消息查找的过程中, 假如没有找到对应的方法实现的话系统会做出一些处理。处理分为两部分, 首先是做了一次 动态方法决议
之后进行 retry
重新查找, 接下来来看一下这部分都做了什么:
动态方法决议
1. _class_resolveMethod
动态方法决议的部分代码:
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
复制代码
通过执行 _class_resolveMethod
方法进行方法决议, 然后在最后通过 goto retry
重新进入消息查找流程, 下面看看这个方法的内部做了什么:
//
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
// 对象方法决议
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
// 类方法决议
_class_resolveClassMethod(cls, sel, inst);
// 判断是否解决
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// 如果没有解决, 再走一遍对象方法决议
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
复制代码
在该方法内部进行了一个判断:
- 如果
cls
不是元类对象的话调用_class_resolveInstanceMethod
。 - 如果传进来的
cls
是元类对象的话就去调用_class_resolveClassMethod
, 调用完以后再去lookUpImpOrNil 查找一遍方法, 并且这里传的 resolver = NO 所以不会再次进入决议
, 如果仍然没有找到就去走一遍对象方法的决议
2. _class_resolveClassMethod 和 _class_resolveInstanceMethod
// _class_resolveInstanceMethod
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, 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 = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
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));
}
}
}
// _class_resolveClassMethod
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
assert(cls->isMetaClass());
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
// _class_getNonMetaClass 方法
// 这里的 cls 应该是元类对象, 通过方法获得对应的类对象然后发送消息
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_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 = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
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));
}
}
}
复制代码
这两个方法的内部实现基本上都是一样的, 唯一不同的地方在于下面这一行:
//
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
//
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);
复制代码
因为 对象方法 和 类方法 的原因在这里进行了分开处理, 这两个流程几乎都是一样的, 所以接下来主要看 对象方法的流程, 有兴趣的可以到源码中再看看类方法的流程。
可以看到这里是给传进来的类对象发送了一个消息, 调用的方法是 SEL_resolveInstanceMethod
, 还有一个参数就是之前一直在查找的方法编号 sel
。接下来去全局搜索看看能不能找到相关的方法
3. resolveInstanceMethod
直接复制 SEL_resolveInstanceMethod
得到的是一个下面的东西:
SEL SEL_resolveInstanceMethod = NULL;
复制代码
然后尝试把前面的 SEL_
去掉, 果然在 NSObject
类中找到了相关内容:
// NSObject.h
+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
// NSObject.m
+ (BOOL)resolveClassMethod:(SEL)sel {
return NO;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO;
}
复制代码
但是这两个方法都只是简单的返回了一个 NO, 并没有做任何处理, 为什么呢?
我觉得这应该是系统给我们提供的一种容错手段吧, 我们可以通过重写这两个方法。拿到传递过来的 SEL, 然后动态的给这个 SEL 添加一个方法实现, 这样就可以有效的避免程序崩溃。
所以之所以说是
动态方法决议
, 关键点就应该在这里。
4. 动态方法决议案例
准备代码:
// Student
@interface Student : NSObject
- (void)sayHello;
+ (void)sayObjc;
@end
@implementation Student
@end
// main.m
Student *student = [[Student alloc] init];
[student sayHello];
复制代码
1. 实例方法
方法决议实现:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
// sayHello 可以是自己定义的任何符号判断
// 这里还可以做一步错误日志上传等操作
if (sel == @selector(sayHello)) {
NSLog(@"方法未找到: %@---%@", self, NSStringFromSelector(sel));
IMP instanceIMP = class_getMethodImplementation(self, @selector(insteadMethod));
Method instanceMethod = class_getInstanceMethod(self, @selector(insteadMethod));
const char * types = method_getTypeEncoding(instanceMethod);
return class_addMethod(self, sel, instanceIMP, types);
}
return NO;
}
- (void)insteadMethod{
NSLog(@"我是替代方法");
}
复制代码
如上所示, 通过在 resolveInstanceMethod
方法里面给传过来的 sel
动态添加了一个方法实现, 就有效的避免了崩溃。而且在这里还可以做一些其他比如错误日志收集等的工作。
2. 类方法
因为类方法是存储在元类中的, 所以在进行类方法动态决议的时候是给元类添加一个方法实现 , 代码如下:
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(sayObjc)) {
NSLog(@"方法未找到: %@---%@", self, NSStringFromSelector(sel));
IMP classIMP = class_getMethodImplementation(objc_getMetaClass("LGStudent"), @selector(insteadClassMethod));
// 类方法存储在元类中, 并且是以实例方法的形式存储
Method classMethod = class_getInstanceMethod(objc_getMetaClass("LGStudent"), @selector(insteadClassMethod));
const char * types = method_getTypeEncoding(classMethod);
return class_addMethod(objc_getMetaClass("LGStudent"), sel, classIMP, types);
}
return NO;
}
+ (void)insteadClassMethod {
NSLog(@"我是替代类方法");
}
复制代码
3. 类方法的 resolveInstanceMethod
因为 类方法
是以对象方法的形式存贮到元类中的, 但是元类和根元类是系统生成的我们无法改变。联想到 isa 的流程图, 在根元类之后会最后指向 NSObject 类, 所以在 NSObject 里面去实现 resolveInstanceMethod
应该可以完成类方法的动态解析:
// NSObject+Test.m
+ (BOOL)resolveInstanceMethod:(SEL)sel {
// sayHello 可以是自己定义的任何符号判断
// 这里还可以做一步错误日志上传等操作
if (sel == @selector(sayHello)) {
NSLog(@"方法未找到: %@---%@", self, NSStringFromSelector(sel));
IMP instanceIMP = class_getMethodImplementation(self, @selector(insteadMethod));
Method instanceMethod = class_getInstanceMethod(self, @selector(insteadMethod));
const char * types = method_getTypeEncoding(instanceMethod);
return class_addMethod(self, sel, instanceIMP, types);
}
return NO;
}
- (void)insteadMethod{
NSLog(@"我是替代方法");
}
复制代码
最后结果是可行的, 对于在 resolverClassMethod
失败只有, 如果在 NSObject
里面有对应的对象方法决议同样可行。
5. 总结
- 在进行消息查找流程中如果没有找到相关实现就会通过调用
_class_resolveMethod
方法进入动态方法决议流程 - 在这个方法里面判断当前的类对象是元类还是普通的类
- 如果不是元类, 说明查找的是
对象方法
, 进入_class_resolveInstanceMethod
流程, 通过objc_msgSend
发送消息去调用resolveInstanceMethod
方法。我们可以通过重写这个方法, 在调用这个方法时拿到传过来的方法SEL
, 动态的给这个SEL
添加一个方法实现, 这样就可以避免崩溃 - 如果是元类, 说明查找的是
类方法
, 会首先进入_class_resolveClassMethod
流程 (流程大致跟对象方法的差不多) - 上面流程结束后, 会调用一次
lookUpImpOrNil
再对当前消息进行一次查找, 因为在决议的过程中可能会把方法添加进去 - 调用
lookUpImpOrNil
如果仍然没有, 就进入到_class_resolveInstance
流程, 因为NSObject
的特殊性, 如果在 NSObject 里面有对当前类方法实现的对象方法决议也能够解决问题
既然 NSObject 里的对象方法决议可以解决不管是对象方法还是类方法全部得问题, 那么是不是可以加一个 NSObject 分类, 然后在这里面解决一切的动态方法决议问题呢?
可以是可以, 但是并不是特别好。
- 把所有的决议都放在这里的话耦合度会变得非常高
- 把项目里的种种方法决议都集中到一起, 逻辑判断也会非常非常多
- 有可能使用的一些三方库中别人也做了这种处理, 这样就容易发生冲突
\