作为处理向 NSNull
发送消息导致的崩溃处理框架,NullSafe
应该是最轻量简洁的了,算起来 50行
代码不算就解决了开发中的NSNull
异常的棘手问题。
问题的出现于处理基于Objective-C
语言的特性:
- OC 中方法调用其实就是向对象发送消息
- 发送出去的消息必须被处理
- 对
NSNull
添加Category
, 这样就无需用代码引入,系统检测到对NSNull
发送任何消息都会自动调用NullSafe
类,如果不想使用,可以用开关关掉
#define NULLSAFE_ENABLED 0
- 接一个
Xcode
警告⚠️消除
#pragma clang diagnostic ignored "-Wgnu-conditional-omitted-operand"
看解释是消除代码中使用了类似三元运算符?:
而没有添加中间变量的warning
,但细看代码,并没有类似的逻辑。
其他关联 ignored
可以参考 使用 #pragma 阻止一些warnings
-
iOS
中对方法的调用,其实就是向目标对象发送各种消息, 造成程序崩溃的主要原因就是因为目标对象无法处理接收到的消息,最后抛出doseNotRecognizeSelector
异常。结合runtime
,在目标对象无法处理接收到的消息的时候,还有补救措施,例如Fast Forward
和Normal Forward
,NullSafe
正是利用了消息转发过程的逻辑,跳过 动态方法解析 和 获取备用接收者 的过程,直接处理消息转发的最后一步。这一步的关键就是重写
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
这个方法,从而获取到 NSMethodSignature
对象,只要获取到的 signature != nil
就可以使用备用调用者来处理此消息。
- (void)forwardInvocation:(NSInvocation *)invocation
这一步处理的目的是对消息进行处理,也就是文章开头提到的第二点,所有发送出去的消息必须要处理才可以。
把新消息对象设置为
nil
因为对nil
发送任何消息都是可以的,这里就把完成了对消息的处理,而不会造成程序崩溃。获取处理该消息的新类都是
NSObject
的子类,如果有可变对象,则用可变对象,若没有则用本对象。
for (Class someClass in @[
[NSMutableArray class],
[NSMutableDictionary class],
[NSMutableString class],
[NSNumber class],
[NSDate class],
[NSData class]
])
一般来说
MutableClass : Class
这样对于同一个类型的类来说,只需要 MutableClass
即可,如果 MutableClass
无法处理此消息,那么 Class
也无法处理。
- 省去了
.h
文件,最大化的让代码变轻
这种方式倒是不常见,为此自己仿写一个实验下效果,
- 建立一个
NSString+Print
的Category
; - 删掉
.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
文件是完全可行的。