废话不多说,直接上代码
#import <objc/runtime.h>
#import <Foundation/Foundation.h>
//若未定义NULLSAFE_ENABLED宏,则定义为1;若定义过NULLSAFE_ENABLED为0,下列代码则不执行,学习此种宏控制的代码执行方式
#ifndef NULLSAFE_ENABLED
#define NULLSAFE_ENABLED 1
#endif
#pragma GCC diagnostic ignored "-Wgnu-conditional-omitted-operand"
@implementation NSNull (NullSafe)
#if NULLSAFE_ENABLED
static NSMutableSet<Class> *classList = nil;
static NSMutableDictionary<NSString *, id> *signatureCache = nil;
static void cacheSignatures()
{
classList = [[NSMutableSet alloc] init];
signatureCache = [[NSMutableDictionary alloc] init];
//get class list 获取到当前注册的所有类的总个数
int numClasses = objc_getClassList(NULL, 0);
//根据 numClasses 个数调整分配类集合 classes 的空间
Class *classes = (Class *)malloc(sizeof(Class) * (unsigned long)numClasses);
//向已分配好内存空间的类集合 classes 中存放元素,并返回个数
numClasses = objc_getClassList(classes, numClasses);
//add to list for checking
for (int i = 0; i < numClasses; i++)
{
//determine if class has a superclass 确定类是否有超类
Class someClass = classes[i];
Class superclass = class_getSuperclass(someClass);
while (superclass)
{
// 如果父类是NSObject,说明已无父类,把class加到classList
// 如果父类不是NSObject,继续遍历其父类,直到父类是NSObject,退出循环
if (superclass == [NSObject class])
{
[classList addObject:someClass];
// 这个地方有些奇怪,classList中只是添加了someClass并没有添加[someClass superclass],为什么要移除?没看懂。而且从实际打印的NSLog来看,执行remove之后classList.count的长度并没有减少。
[classList removeObject:[someClass superclass]];
break;
}
superclass = class_getSuperclass(superclass);
}
}
//free class list
free(classes);
}
//NSNull对象找不到方法就会走这个方法。
//消息转发机制使用从这个方法中获取的信息来创建NSInvocation对象。因此我们必须重写这个方法,为给定的selector提供一个合适的方法签名。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
//look up method signature 取selector方法的签名
NSMethodSignature *signature = [super methodSignatureForSelector:selector];
if (!signature)
{
//check implementation cache first
NSString *selectorString = NSStringFromSelector(selector);
signature = signatureCache[selectorString];
if (!signature)
{
@synchronized([NSNull class])
{
//check again, in case it was resolved while we were waitimg
signature = signatureCache[selectorString];
if (!signature)
{
//not supported by NSNull, search other classes
if (signatureCache == nil)
{
if ([NSThread isMainThread])
{
cacheSignatures();
}
else
{
dispatch_sync(dispatch_get_main_queue(), ^{
cacheSignatures();
});
}
}
//find implementation
for (Class someClass in classList)
{
//此方法会查询someClass及其子类是否有实现selector方法
if ([someClass instancesRespondToSelector:selector])
{
signature = [someClass instanceMethodSignatureForSelector:selector];
break;
}
}
//若获得了selector方法签名,赋值signatureCache[selectorString] = signature(学习此种三目运算使用技巧);
//若未获得,把静态字典signatureCache[selectorString] = [NSNull null];这样处理是为了若再次查找此selector方法的时候,直接在上面“signature = signatureCache[selectorString];”中signature获得为NSNull,这样就会走下面的else判断
//cache for next time
signatureCache[selectorString] = signature ?: [NSNull null];
}
else if ([signature isKindOfClass:[NSNull class]])
{
//若未实现,就把selector方法签名置为nil,不会走下面forwardInvocation,直接crash,因为所有类都找不到此方法
signature = nil;
}
}
}
}
//signature若不为nil,接下来会走forwardInvocation方法
return signature;
}
//运行时系统会在这一步给消息接收者最后一次机会将消息转发给其它对象。对象会创建一个表示消息的NSInvocation对象,把与尚未处理的消息有关的全部细节都封装在anInvocation中,包括selector,目标(target)和参数。
- (void)forwardInvocation:(NSInvocation *)invocation
{
//关键点在这里!将消息转发给nil
invocation.target = nil;
[invocation invoke];
}
//源码学习点:
//
//1、宏定义控制代码执行;静态变量作缓存防止再次获取筛选浪费资源;三目运算技巧;NSMutableSet集合无重复特点;
//2、runtime的使用;
//3、消息转发:对象找不到方法会调methodSignatureForSelector获取方法签名。获得方法签名后调forwardInvocation,此方法可以修改消息转发对象为nil;获取不到签名,直接crash掉,说明调用了一个未知方法,即写的代码出了问题;
//4、为什么要找所有根父类的类集合呢?是因为instancesRespondToSelector方法会先查询其子类是否有实现selector方法,再从子类的父类查找,如此直到查找到自身类;
#endif
@end