Objective-C是一门动态语言,一个函数是由一个selector(SEL),和一个implement(IML)组成的。
执行一个方法时如果系统找不到方法会给几次机会寻找方法,实在没有此方法就会抛出异常。

运行时查找函数的步骤

由图可见

  1. - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
  2. - (void)forwardInvocation:(NSInvocation *)anInvocation
  3.  
  4. 这两个函数是最后一个寻找IML的机会。这个函数让重载方有机会抛出一个函数的签名,再由后面的forwardInvocation:去执行。

源码解读

  1. #ifndef NULLSAFE_ENABLED
  2. #define NULLSAFE_ENABLED 1
  3. #endif
  4.  
  5. // 忽略warning
  6. // 三木运算符忽略中间一木导致的警告
  7. #pragma GCC diagnostic ignored "-Wgnu-conditional-omitted-operand"

关闭警告

  1. // 调用methodSignatureForSelector 方法
  2. - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
  3. {
  4. // 保持原子性,添加同步锁,防止被修改
  5. @synchronized([self class])
  6. {
  7. }
  8. }
  1. // 本类父类种寻找是否拥有此方法,拥有方法则直接返回 signature
  2. NSMethodSignature *signature = [super methodSignatureForSelector:selector];
  3. if (!signature)
  4. {
  5. }
  6. return signature;
  1. // 本类父类种寻找是否拥有此方法
  2. NSMethodSignature *signature = [super methodSignatureForSelector:selector];
  3. if (!signature)
  4. {
  5. // 方法列表
  6. static NSMutableSet *classList = nil;
  7. // 缓存方法字典
  8. static NSMutableDictionary *signatureCache = nil;
  9. if (signatureCache == nil)
  10. {
  11. classList = [[NSMutableSet alloc] init];
  12. signatureCache = [[NSMutableDictionary alloc] init];
  13.  
  14. //get class list
  15.  
  16. /*
  17. 分析:该函数的作用是获取已经注册的类,它需要传入两个参数,第一个参数 buffer :已分配好内存空间的数组,第二个参数 bufferCount :数组中可存放元素的个数,返回值是注册的类的总数。
  18. 当参数 bufferCount 值小于注册的类的总数时,获取到的是注册类的集合的任意子集
  19. 第一个参数传 NULL 时将会获取到当前注册的所有的类,此时可存放元素的个数为0,因此第二个参数可传0,返回值为当前注册的所有类的总数。
  20. */
  21. // 获取项目中所有类的个数
  22. int numClasses = objc_getClassList(NULL, );
  23.  
  24. // 调整一个classes的大小 = 获取一个class的 size * 所有的class
  25. Class *classes = (Class *)malloc(sizeof(Class) * (unsigned long)numClasses);
  26.  
  27. // 获取项目中class的个数
  28. numClasses = objc_getClassList(classes, numClasses);
  29.  
  30. // 初始化被排除的 NSMutableSet
  31. NSMutableSet *excluded = [NSMutableSet set];
  32.  
  33. for (int i = ; i < numClasses; i++)
  34. {
  35. // 判断classes【i】 是否有superclass
  36. Class someClass = classes[i];
  37. Class superclass = class_getSuperclass(someClass);
  38.  
  39. // 循环找出 someClass 的所有的superclass
  40. while (superclass)
  41. {
  42. // 当superclass存在 判断是否等于 NSObject
  43. if (superclass == [NSObject class])
  44. {
  45. // 等于 NSObject 加入 classList
  46. [classList addObject:someClass];
  47. break;
  48. }
  49. [excluded addObject:NSStringFromClass(superclass)];
  50. superclass = class_getSuperclass(superclass);
  51. }
  52. }
  53.  
  54. // 上面循环走完之后 查找到所有继承自NSObject的类
  55.  
  56. // 基于 NSObject 的类 中 删除 不基于 NSObject 类
  57. for (Class someClass in excluded)
  58. {
  59. [classList removeObject:someClass];
  60. }
  61.  
  62. // 释放上面创建的 classes
  63. free(classes);
  64. }
  1. 经过上面代码获取项目中的类的列表和缓存
  1. // check implementation cache first
  2. NSString *selectorString = NSStringFromSelector(selector);
  3. signature = signatureCache[selectorString];
  4. if (!signature)
  5. {
  6. for (Class someClass in classList)
  7. {
  8. // 判断 这个基于NSObject类的子类是否能够响应传入的方法
  9. if ([someClass instancesRespondToSelector:selector])
  10. {
  11. // someClass类能够响应selector方法
  12. // 返回NSMethodSignature对象,这个对象包含被标示的实例方法的描述。
  13. signature = [someClass instanceMethodSignatureForSelector:selector];
  14. break;
  15. }
  16. }
  17.  
  18. // cache for next time
  19. signatureCache[selectorString] = signature ?: [NSNull null];
  20. }
  21. else if ([signature isKindOfClass:[NSNull class]])
  22. {
  23. // 缓存是NSNull类型的话 将需要执行的方法置为nil
  24. signature = nil;
  25. }
  1. // forwardInvocation:将选择器转发给一个真正实现了该消息的对象。
  2. - (void)forwardInvocation:(NSInvocation *)invocation
  3. {
  4. // 将target = nil ,不发送
  5. invocation.target = nil;
  6. [invocation invoke];
  7. }

总结:
当我们给一个NSNull对象发送消息的话,可能会崩溃(null是有内存的),而发送给nil的话,是不会崩溃的。

作者就是使用了这么一个原理,把发送给NSNull的而NSNull又无法处理的消息经过如下几步处理:

  1. 创建缓存,缓存项目中类的所有类名。
  2. 遍历缓存,寻找是否已经有可以执行此方法的类。
  3. 如果有的话,返回这个NSMethodSignature。
  4. 如果没有的话,返回nil,崩溃
  5. 如果有的话,[invocation invokeWithTarget:nil];将消息转发给nil。

那么,如何判断NSNull无法处理这个消息呢,在OC中,系统如果对某个实例发送消息之后,它(及其父类)无法处理(比如,没有这个方法等),系统就会发送methodSignatureForSelector消息,如果这个方法返回非空,那么就去执行返回的方法,如果为nil,则发送forwardInvocation消息。

这样就完成整个转发链了。

NullSafe基于Runtime的深度解析的更多相关文章

  1. mybatis 3.x源码深度解析与最佳实践(最完整原创)

    mybatis 3.x源码深度解析与最佳实践 1 环境准备 1.1 mybatis介绍以及框架源码的学习目标 1.2 本系列源码解析的方式 1.3 环境搭建 1.4 从Hello World开始 2 ...

  2. spring5 源码深度解析----- 被面试官给虐懵了,竟然是因为我不懂@Configuration配置类及@Bean的原理

    @Configuration注解提供了全新的bean创建方式.最初spring通过xml配置文件初始化bean并完成依赖注入工作.从spring3.0开始,在spring framework模块中提供 ...

  3. Go netpoll I/O 多路复用构建原生网络模型之源码深度解析

    导言 Go 基于 I/O multiplexing 和 goroutine 构建了一个简洁而高性能的原生网络模型(基于 Go 的I/O 多路复用 netpoll),提供了 goroutine-per- ...

  4. 深度解析Maven

    此文来源于: https://www.cnblogs.com/hafiz/p/8119964.html 带你深度解析Maven   一.What`s Maven? Maven是基于项目对象模型(POM ...

  5. Spring源码深度解析之数据库连接JDBC

    Spring源码深度解析之数据库连接JDBC JDBC(Java Data Base Connectivity,Java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供 ...

  6. [WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析

    [WebKit内核] JavaScript引擎深度解析--基础篇(一)字节码生成及语法树的构建详情分析 标签: webkit内核JavaScriptCore 2015-03-26 23:26 2285 ...

  7. Deep Learning模型之:CNN卷积神经网络(一)深度解析CNN

    http://m.blog.csdn.net/blog/wu010555688/24487301 本文整理了网上几位大牛的博客,详细地讲解了CNN的基础结构与核心思想,欢迎交流. [1]Deep le ...

  8. (转载)(收藏)OceanBase深度解析

    一.OceanBase不需要高可靠服务器和高端存储 OceanBase是关系型数据库,包含内核+OceanBase云平台(OCP).与传统关系型数据库相比,最大的不同点, 是OceanBase是分布式 ...

  9. Kafka深度解析

    本文转发自Jason’s Blog,原文链接 http://www.jasongj.com/2015/01/02/Kafka深度解析 背景介绍 Kafka简介 Kafka是一种分布式的,基于发布/订阅 ...

随机推荐

  1. Python学习笔记(十三)

    Python学习笔记(十三): 模块 包 if name == main 软件目录结构规范 作业-ATM+购物商城程序 1. 模块 1. 模块导入方法 import 语句 import module1 ...

  2. hashlib模块--摘要算法

    算法介绍: Python的hashlib提供了常见的摘要算法:MD5,SHA()等. 摘要算法,又称哈希算法,散列算法.通过一个函数,吧任意长度的字符串转换为固定长度的字符串(16进制) 摘要算法就是 ...

  3. JavaScript函数之作用域 / 作用链域 / 预解析

    关于作用域和作用链域的问题,很多文章讲的都很详细,本文属于摘录自己觉得对自己有价值的部分,留由后用,仅供参考,需要查看详细信息请点击我给出的原文链接查看原文件 做一个有爱的搬运工~~ -------- ...

  4. C#实现局域网内远程开机

    1.远程开机原理 远程开机Wake on LAN(WOL),俗称远程唤醒,远程唤醒的实现主要是向目标主机发送特殊格式的数据包,是AMD公司制作的MagicPacket这套软件以生成网络唤醒所需要的特殊 ...

  5. Fork/Join-Java并行计算框架

    Java在JDK7之后加入了并行计算的框架Fork/Join,可以解决我们系统中大数据计算的性能问题.Fork/Join采用的是分治法,Fork是将一个大任务拆分成若干个子任务,子任务分别去计算,而J ...

  6. 使用VS2013 + EF6 + .NET4.5 连接Mysql数据库

    1.安装插件 在使用Visual Studio 2013添加ADO.NET实体数据模型新建连接时,默认是没有Mysql选项的.此时我们需要安装两个东西: 1.mysql-for-visualstudi ...

  7. 理解vue中的scope的使用

    理解vue中的scope的使用 我们都知道vue slot插槽可以传递任何属性或html元素,但是在调用组件的页面中我们可以使用 template scope="props"来获取 ...

  8. python 文件相关知识

    字符编码相关 什么是字符编码 字符编码的类型 字符编码的使用 python2和python里字符编码的区别 文件的相关 文件的基础操作 打开文件的模式 字符编码 什么是字符编码在计算机里只识别二进制, ...

  9. PHP的重载及魔术方法

    首先你要知道什么是php的魔术方法,它不是变魔术的,如果你想学习变魔术来错地方了哦! 定义:PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法.所以在定义类方法时,除了上述魔术方法,建议不 ...

  10. iOS 输入时键盘处理问题

    最正规的办法,用通知 step 1:在进入视图的时候添加监视:(viewDidLoad什么的) //监听键盘的通知 [[NSNotificationCenter defaultCenter] addO ...