NullSafe基于Runtime的深度解析
Objective-C是一门动态语言,一个函数是由一个selector(SEL),和一个implement(IML)组成的。
执行一个方法时如果系统找不到方法会给几次机会寻找方法,实在没有此方法就会抛出异常。
由图可见
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector - (void)forwardInvocation:(NSInvocation *)anInvocation 这两个函数是最后一个寻找IML的机会。这个函数让重载方有机会抛出一个函数的签名,再由后面的forwardInvocation:去执行。
源码解读
#ifndef NULLSAFE_ENABLED #define NULLSAFE_ENABLED 1 #endif // 忽略warning // 三木运算符忽略中间一木导致的警告 #pragma GCC diagnostic ignored "-Wgnu-conditional-omitted-operand"
// 调用methodSignatureForSelector 方法 - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { // 保持原子性,添加同步锁,防止被修改 @synchronized([self class]) { } }
// 本类父类种寻找是否拥有此方法,拥有方法则直接返回 signature NSMethodSignature *signature = [super methodSignatureForSelector:selector]; if (!signature) { } return signature;
// 本类父类种寻找是否拥有此方法 NSMethodSignature *signature = [super methodSignatureForSelector:selector]; if (!signature) { // 方法列表 static NSMutableSet *classList = nil; // 缓存方法字典 static NSMutableDictionary *signatureCache = nil; if (signatureCache == nil) { classList = [[NSMutableSet alloc] init]; signatureCache = [[NSMutableDictionary alloc] init]; //get class list /* 分析:该函数的作用是获取已经注册的类,它需要传入两个参数,第一个参数 buffer :已分配好内存空间的数组,第二个参数 bufferCount :数组中可存放元素的个数,返回值是注册的类的总数。 当参数 bufferCount 值小于注册的类的总数时,获取到的是注册类的集合的任意子集 第一个参数传 NULL 时将会获取到当前注册的所有的类,此时可存放元素的个数为0,因此第二个参数可传0,返回值为当前注册的所有类的总数。 */ // 获取项目中所有类的个数 int numClasses = objc_getClassList(NULL, 0); // 调整一个classes的大小 = 获取一个class的 size * 所有的class Class *classes = (Class *)malloc(sizeof(Class) * (unsigned long)numClasses); // 获取项目中class的个数 numClasses = objc_getClassList(classes, numClasses); // 初始化被排除的 NSMutableSet NSMutableSet *excluded = [NSMutableSet set]; for (int i = 0; i < numClasses; i++) { // 判断classes【i】 是否有superclass Class someClass = classes[i]; Class superclass = class_getSuperclass(someClass); // 循环找出 someClass 的所有的superclass while (superclass) { // 当superclass存在 判断是否等于 NSObject if (superclass == [NSObject class]) { // 等于 NSObject 加入 classList [classList addObject:someClass]; break; } [excluded addObject:NSStringFromClass(superclass)]; superclass = class_getSuperclass(superclass); } } // 上面循环走完之后 查找到所有继承自NSObject的类 // 基于 NSObject 的类 中 删除 不基于 NSObject 类 for (Class someClass in excluded) { [classList removeObject:someClass]; } // 释放上面创建的 classes free(classes); }
经过上面代码获取项目中的类的列表和缓存
// check implementation cache first NSString *selectorString = NSStringFromSelector(selector); signature = signatureCache[selectorString]; if (!signature) { for (Class someClass in classList) { // 判断 这个基于NSObject类的子类是否能够响应传入的方法 if ([someClass instancesRespondToSelector:selector]) { // someClass类能够响应selector方法 // 返回NSMethodSignature对象,这个对象包含被标示的实例方法的描述。 signature = [someClass instanceMethodSignatureForSelector:selector]; break; } } // cache for next time signatureCache[selectorString] = signature ?: [NSNull null]; } else if ([signature isKindOfClass:[NSNull class]]) { // 缓存是NSNull类型的话 将需要执行的方法置为nil signature = nil; }
// forwardInvocation:将选择器转发给一个真正实现了该消息的对象。 - (void)forwardInvocation:(NSInvocation *)invocation { // 将target = nil ,不发送 invocation.target = nil; [invocation invoke]; }
总结:
当我们给一个NSNull对象发送消息的话,可能会崩溃(null是有内存的),而发送给nil的话,是不会崩溃的。
作者就是使用了这么一个原理,把发送给NSNull的而NSNull又无法处理的消息经过如下几步处理:
- 创建缓存,缓存项目中类的所有类名。
- 遍历缓存,寻找是否已经有可以执行此方法的类。
- 如果有的话,返回这个NSMethodSignature。
- 如果没有的话,返回nil,崩溃
- 如果有的话,[invocation invokeWithTarget:nil];将消息转发给nil。
那么,如何判断NSNull无法处理这个消息呢,在OC中,系统如果对某个实例发送消息之后,它(及其父类)无法处理(比如,没有这个方法等),系统就会发送methodSignatureForSelector消息,如果这个方法返回非空,那么就去执行返回的方法,如果为nil,则发送forwardInvocation消息。
这样就完成整个转发链了。
少而好学,如日出之阳;
壮而好学,如日中之光;
老而好学,如炳烛之明。