场景
对于 NSDictionary
初始化的时候,如果设置了 nil
会导致 Crash
,报 -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]
代码
NSString *str = nil;
NSDictionary *dict = @{@"string" : str};
NSLog(@"%@", dict);
异常如下
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]'
根据错误信息反馈,initWithObjects:forKeys:count:
这个方法中有非法操作,下面通过 Runtime
对这个方法进行内容判断,达到避免同场景下的操作下造成崩溃现象。
原理
通过 Runtime
在加载类和分类的时候,将系统 initWithObjects:forKeys:count:
方法跟自己所写的方法进行替换,然后在自己写的方法里面对值安全判断,然后再调用系统的方法,创建一个实例对象。
步骤
首先,新建一个 NSDictionary
分类,在 .m
文件中,重写 +(void)load
方法,从错误信息中可以分析到在__NSPlaceholderDictionary
这个类中的 initWithObjects:forKeys:count:
方法出现了崩溃现象,所以我们在传入 Class
的时候需要传入 __NSPlaceholderDictionary
这个类,同样的在替换一些分类方法的时候,也是通过这样的一个方式去替换的。
+ (void)load {
// 获取系统 initWithObjects:forKeys:count: 方法,这里是获取一个实例方法利用 class_getInstanceMethod 去获取
// class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name) 这里 cls 是所需要替换方法的类, name 是需要替换方法的 SEL
Method system_initWithObjectsForKeysCountMethod = class_getInstanceMethod(NSClassFromString(@"__NSPlaceholderDictionary"), @selector(initWithObjects:forKeys:count:));
// 拿到自己所写的 gy_initWithObjects:forKeys:count:
Method gy_initWithObjectsForKeysCountMethod = class_getInstanceMethod(self, @selector(gy_initWithObjects:forKeys:count:));
// 将系统方法和自己所写的方法进行替换
// method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 需要替换的方法
method_exchangeImplementations(system_initWithObjectsForKeysCountMethod, gy_initWithObjectsForKeysCountMethod);
}
自己实现的 gy_initWithObjects:forKeys:count:
方法
- (instancetype)gy_initWithObjects:(id _Nonnull const [])objects forKeys:(id<NSCopying> _Nonnull const [])keys count:(NSUInteger)cnt {
// 此处因为传入的参数是 C 语言数组,所以这里也通过 C 语言数组操作
NSUInteger index = 0; // 数组下标,用于将数据存入正确的位置
id objectsArray[cnt]; // 值 数组
id<NSCopying> keysArray[cnt]; // 键 数组
// 遍历传入的参数数组,对正常的数据放入新创建的数组中
for (int i = 0; i < cnt; i++) {
if (objects[i] != nil && keys[i] != nil) {
objectsArray[index] = objects[i];
keysArray[index] = keys[i];
index++;
}
}
// 调用系统方法,这里一定是 gy_initWithObjects:forKeys:count: 方法,因为已经方法替换了,调用这个方法实际上是调用系统原来的方法
// 将处理好的健值数组作为新的健值数组传入
return [self gy_initWithObjects:objectsArray forKeys:keysArray count:index];
}
这样就完成了对 -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]
这个异常的处理。
*** -[__NSDictionaryM setObject:forKey:]: object cannot be nil (key: string)
对于这种错误信息,也同样采用这种方式去处理
+ (void)load {
Method system_setObjectForKeyMethod = class_getInstanceMethod(NSClassFromString(@"__NSDictionaryM"), @selector(setObject:forKey:));
Method gy_setObjectForKeyMethod = class_getInstanceMethod(self, @selector(gy_setObject:forKey:));
method_exchangeImplementations(system_setObjectForKeyMethod, gy_setObjectForKeyMethod);
}
自己实现的 gy_setObject:forKey:
方法
- (void)gy_setObject:(id)anObject forKey:(id <NSCopying>)aKey {
if (anObject != nil && aKey != nil) {
[self gy_setObject:anObject forKey:aKey];
}
}
这样对 NSDictionary
一些常见的数据不安全造成崩溃的处理就完成了。