NullSafe 学习

作为处理向 NSNull 发送消息导致的崩溃处理框架,NullSafe 应该是最轻量简洁的了,算起来 50行 代码不算就解决了开发中的NSNull异常的棘手问题。

问题的出现于处理基于Objective-C语言的特性:

  • OC 中方法调用其实就是向对象发送消息
  • 发送出去的消息必须被处理
  1. NSNull 添加 Category, 这样就无需用代码引入,系统检测到对 NSNull 发送任何消息都会自动调用 NullSafe 类,如果不想使用,可以用开关关掉
#define NULLSAFE_ENABLED 0
  1. 接一个 Xcode 警告⚠️消除
#pragma clang diagnostic ignored "-Wgnu-conditional-omitted-operand"

看解释是消除代码中使用了类似三元运算符?:而没有添加中间变量的warning,但细看代码,并没有类似的逻辑。
其他关联 ignored 可以参考 使用 #pragma 阻止一些warnings

  1. iOS 中对方法的调用,其实就是向目标对象发送各种消息, 造成程序崩溃的主要原因就是因为目标对象无法处理接收到的消息,最后抛出 doseNotRecognizeSelector 异常。结合 runtime,在目标对象无法处理接收到的消息的时候,还有补救措施,例如 Fast ForwardNormal Forward, NullSafe 正是利用了消息转发过程的逻辑,跳过 动态方法解析获取备用接收者 的过程,直接处理消息转发的最后一步。这一步的关键就是重写
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector

这个方法,从而获取到 NSMethodSignature 对象,只要获取到的 signature != nil 就可以使用备用调用者来处理此消息。

- (void)forwardInvocation:(NSInvocation *)invocation

这一步处理的目的是对消息进行处理,也就是文章开头提到的第二点,所有发送出去的消息必须要处理才可以。

  1. 把新消息对象设置为 nil 因为对 nil 发送任何消息都是可以的,这里就把完成了对消息的处理,而不会造成程序崩溃。

  2. 获取处理该消息的新类都是 NSObject 的子类,如果有可变对象,则用可变对象,若没有则用本对象。

for (Class someClass in @[
            [NSMutableArray class],
            [NSMutableDictionary class],
            [NSMutableString class],
            [NSNumber class],
            [NSDate class],
            [NSData class]
        ])

一般来说

MutableClass : Class

这样对于同一个类型的类来说,只需要 MutableClass 即可,如果 MutableClass 无法处理此消息,那么 Class 也无法处理。

  1. 省去了 .h 文件,最大化的让代码变轻
    这种方式倒是不常见,为此自己仿写一个实验下效果,
  • 建立一个 NSString+PrintCategory;
  • 删掉 .h 文件;
  • 删掉 .m 中的 #import "NSString+Print.h"
  • 加入类库引用 #import
  • 随便添加个类方法和实例方法
    完整代码(NSString+Print.m)
#import 

@implementation NSString (Print)

+ (void)print:(NSString *)str {
    NSLog(@" %@", str);
}

- (void)print:(NSString *)str {
    NSLog(@" %@", str);
}

@end

在其他文件中测试该方法,由于NSString 和 父类 NSObject 中都不含有

- (void) print;  //不含有此实例方法
+ (void) print; //不含有此类方法

所以添加的方法只能在运行阶段识别,所以在编译时期无法识别该方法,导致编译阶段无法通过


借助 msg_send 函数,手动发送此消息验证

((void (*)(id, SEL, id))objc_msgSend)([NSString class], @selector(print:), str);

当然此时会报 Undeclared selector 'print:'warning 不过不影响编译.

最后可以测试通过


可见在无需与其他文件进行交互的前提下,单独的 .m 文件是完全可行的。

你可能感兴趣的:(NullSafe 学习)