iOS探索 -- 动态方法决议分析

通过前面的研究知道了, 方法的过程实际上就是 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 分类, 然后在这里面解决一切的动态方法决议问题呢?

可以是可以, 但是并不是特别好。

  1. 把所有的决议都放在这里的话耦合度会变得非常高
  2. 把项目里的种种方法决议都集中到一起, 逻辑判断也会非常非常多
  3. 有可能使用的一些三方库中别人也做了这种处理, 这样就容易发生冲突

\

猜你喜欢

转载自juejin.im/post/7107307119702966303