写在前面
OC上常见崩溃一般不亚于 数组越界以及字典设置为nil。
虽然大家基本都知道这些情况下,程序会表示抱歉,我要崩溃的,但是大多数情况下传入进来的都是一个变量,变量真正的值有时候就会正在的出乎程序员的意料,比如过大导致越界,或是尽然是空的。如果说这种意外情况无法避免,那么只能从侧面采取保护措施。
我先整理下常见的崩溃方法:
NSArray:
objectAtIndex:
NSMutableArray:
addObject:
insertObject:atIndex:
removeObjectAtIndex:
replaceObjectAtIndex:withObject:
NSMutableDictionary:
setObject:forKey:
- 取值:index超出array的索引范围
- 添加:插入的object为nil或者Null
- 插入:index大于count、插入的object为nil或者Null
- 删除:index超出array的索引范围
- 替换:index超出array的索引范围、替换的object为nil或者Null
现在市面上主流的两种结局方法是:
- 使用runtime hook系统的这几个方法
- 使用分类的形式重新封装一层方法
两种方法当然各有利弊,hack方便,但是风险不可控,使用分类的方式反锁,风险较易控制。
捕捉到错误之后,可以使用Crashlytics上传崩溃日志,这样就可以自我捕捉这些错误而不让系统抛出exception而崩溃。
个人推荐还是使用稳妥的方法,用分类重新封装一层方法的形式。
代替exception使用日志系统上报错误
日志上报使用的是fabric,有关fabric的使用,自行去google,这里不做过多讲解。
关键技术是使用fabric的方法recordCustomExceptionName:reason:frameArray:
/**
* This method can be used to record a single exception structure in a report. This is particularly useful
* when your code interacts with non-native languages like Lua, C#, or Javascript. This call can be
* expensive and should only be used shortly before process termination. This API is not intended be to used
* to log NSException objects. All safely-reportable NSExceptions are automatically captured by
* Crashlytics.
*
* @param name The name of the custom exception
* @param reason The reason this exception occured
* @param frameArray An array of CLSStackFrame objects
*/
- (void)recordCustomExceptionName:(NSString *)name reason:(nullable NSString *)reason frameArray:(CLS_GENERIC_NSARRAY(CLSStackFrame *) *)frameArray;
注释大致就是说可以自行发送日志到fabric,所以就有了下面的使用方法:
static void Log(NSString *fmt, ...) {
NSMutableString *reportStr = [NSMutableString string];
va_list ap;
va_start(ap, fmt);
NSString *content = [[NSString alloc] initWithFormat:fmt arguments:ap];
[reportStr appendString:@"***Terminating app due to uncaught exception\r\n"];
[reportStr appendFormat:@"***reason:-%@\r\n", content];
va_end(ap);
[reportStr appendFormat:@"*** First throw call stack:\n%@", [NSThread callStackSymbols]];
NSArray<NSString *> *callStackSymbols = [NSThread callStackSymbols];
NSMutableArray<CLSStackFrame *> *stackFrames = [NSMutableArray arrayWithCapacity:callStackSymbols.count];
for (NSString *callStachSymbol in callStackSymbols) {
[stackFrames addObject:[CLSStackFrame stackFrameWithSymbol:callStachSymbol]];
}
#ifdef DEBUG
[NSException raise:NSInvalidArgumentException format:@"***reason%@", content];
#else
[CrashlyticsKit recordCustomExceptionName:@"自捕获崩溃" reason:content frameArray:stackFrames];
#endif
YXLogError(@"%@", reportStr);
}
最后fabirc上捕捉到的日志就是non-fatals
使用RunTime hack系统方法的形式实现
工具类继承至NSObject,在load类方法中实现hack技术,分别hook住相应的方法
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// NSArray
[self swizzleInstanceMethodWithClass:NSClassFromString(@"__NSArrayI") originalSelector:@selector(objectAtIndex:) swizzledMethod:@selector(safeObjectAtIndex:)];
// NSMutableArray
[self swizzleInstanceMethodWithClass:NSClassFromString(@"__NSArrayM") originalSelector:@selector(objectAtIndex:) swizzledMethod:@selector(safeObjectAtIndex:)];
// [self swizzleInstanceMethodWithClass:NSClassFromString(@"__NSArrayM") originalSelector:@selector(addObject:) swizzledMethod:@selector(safeAddObject:)];
[self swizzleInstanceMethodWithClass:NSClassFromString(@"__NSArrayM") originalSelector:@selector(replaceObjectAtIndex:withObject:) swizzledMethod:@selector(safeReplaceObjectAtIndex:withObject:)];
[self swizzleInstanceMethodWithClass:NSClassFromString(@"__NSArrayM") originalSelector:@selector(insertObject:atIndex:) swizzledMethod:@selector(safeInsertObject:atIndex:)];
// NSDictionary
// [self swizzleClassMethodWithClass:[NSDictionary class] originalSelector:@selector(dictionaryWithObjects:forKeys:count:) swizzledMethod:@selector(safeDictionaryWithObjects:forKeys:count:)];
// NSMutableDictionary
[self swizzleInstanceMethodWithClass:NSClassFromString(@"__NSDictionaryM") originalSelector:@selector(setObject:forKey:) swizzledMethod:@selector(safeSetObject:forKey:)];
});
}
NSArray
@interface NSArray (Safte)
@end
@implementation NSArray (Safte)
- (id)safeObjectAtIndex:(NSUInteger)index {
@autoreleasepool {
if (self.count > index) {
return [self safeObjectAtIndex:index];
}else {
Log(@"[%@ %@] index %lu beyond bounds [0 .. %lu]",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd),
(unsigned long)index,
MAX((unsigned long)self.count - 1, 0));
return nil;
}
}
}
@end
NSMutableArray
@interface NSMutableArray (Safte)
@end
@implementation NSMutableArray (Safte)
- (id)safeObjectAtIndex:(NSUInteger)index {
@autoreleasepool {
if (self.count > index) {
return [self safeObjectAtIndex:index];
}else {
Log(@"[%@ %@] index %lu beyond bounds [0 .. %lu]",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd),
(unsigned long)index,
MAX((unsigned long)self.count - 1, 0));
return nil;
}
}
}
- (void)safeAddObject:(id)anObject {
@autoreleasepool {
if (anObject) {
[self safeAddObject:anObject];
}else {
Log(@"[%@ %@], nil object. object cannot be nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
}
}
}
- (void)safeReplaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject {
@autoreleasepool {
if (index >= self.count) {
Log(@"[%@ %@] index %lu beyond bounds [0 .. %lu].",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd),
(unsigned long)index,
MAX((unsigned long)self.count - 1, 0));
return;
}else if (!anObject) {
Log(@"[%@ %@] nil object. object cannot be nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
return;
}
[self safeReplaceObjectAtIndex:index withObject:anObject];
}
}
- (void)safeInsertObject:(id)anObject atIndex:(NSUInteger)index {
@autoreleasepool {
if (index > self.count)
{
Log(@"[%@ %@] index %lu beyond bounds [0...%lu].",
NSStringFromClass([self class]),
NSStringFromSelector(_cmd),
(unsigned long)index,
MAX((unsigned long)self.count - 1, 0));
return;
}
if (!anObject)
{
Log(@"[%@ %@] nil object. object cannot be nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
return;
}
[self safeInsertObject:anObject atIndex:index];
}
}
@end
NSDictionary
@interface NSDictionary (Safte)
@end
@implementation NSDictionary (Safte)
+ (instancetype)safeDictionaryWithObjects:(const id _Nonnull __unsafe_unretained *)objects forKeys:(const id<NSCopying> _Nonnull __unsafe_unretained *)keys count:(NSUInteger)cnt {
@autoreleasepool {
id validObjects[cnt];
id<NSCopying> validKeys[cnt];
NSUInteger count = 0;
for (NSUInteger i = 0; i < cnt; i++)
{
if (objects[i] && keys[i])
{
validObjects[count] = objects[i];
validKeys[count] = keys[i];
count ++;
}
else
{
Log(@"[%@ %@] NIL object or key at index%lu.",
NSStringFromClass(self),
NSStringFromSelector(_cmd),
(unsigned long)i);
}
}
return [self safeDictionaryWithObjects:validObjects forKeys:validKeys count:count];
}
}
@end
NSMuatbleDictionary
@interface NSMutableDictionary (Safte)
@end
@implementation NSMutableDictionary (Safte)
- (void)safeSetObject:(id)anObject forKey:(id<NSCopying>)aKey {
@autoreleasepool {
if (!aKey)
{
Log(@"[%@ %@] nil key. key cannot be nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
return;
}
if (!anObject)
{
Log(@"[%@ %@] nil object. object cannot be nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
return;
}
[self safeSetObject:anObject forKey:aKey];
}
}
@end
安全容器方法
分装一层分类,规定以后团队都使用这个分类方法
NSArray
@implementation NSArray (Safety)
- (id)at:(NSUInteger)i {
if (i < self.count) {
return self[i];
} else {
Log(@"NSArray objectAtIndex beyond the bound: %lu is beyond the count:%lu!", (unsigned long)i, (unsigned long)self.count);
return nil;
}
}
+ (instancetype)create:(id)firstObj, ... {
NSMutableArray *array = [NSMutableArray new];
va_list args;
va_start(args, firstObj);
for (id arg = firstObj; arg != nil; arg = va_arg(args, id)) {
[array add:arg];
}
va_end(args);
if ([self isEqual:NSMutableDictionary.class]) {
return array;
} else {
return [array copy];
}
}
@end
NSDictionary
@implementation NSDictionary (Safety)
+ (instancetype)kv:(id)firstObj, ... {
NSMutableDictionary *dic = [NSMutableDictionary new];
va_list args;
va_start(args, firstObj);
id key, value;
int i = 0;
for (id arg = firstObj; arg != nil; arg = va_arg(args, id)) {
if (i % 2 == 0) {
key = arg;
} else {
value = arg;
[dic key:key value:value];
}
i++;
}
va_end(args);
if ([self isEqual:NSMutableDictionary.class]) {
return dic;
} else {
return dic.copy;
}
}
@end
NSMutableArray
@implementation NSMutableArray (Safety)
- (void)add:(id)object {
if (object == nil) {
Log(@"NSMutableArray added a nil object!");
return;
}
[self addObject:object];
}
- (void)insert:(id)o at:(NSUInteger)i {
if (o == nil) {
Log(@"NSMutableArray inserted a nil object!");
} else if (i > self.count) { //这里得是> 而不能>=,因为insert能插入到最后一个
Log(@"NSArray insertObject:atIndex: beyond the bound: %lu is beyond the count:%lu!", (unsigned long)i, (unsigned long)self.count);
} else {
[self insertObject:o atIndex:i];
}
}
- (void)removeAt:(NSUInteger)i {
if (i < self.count) {
[self removeObjectAtIndex:i];
} else {
Log(@"NSArray removeObjectAtIndex beyond the bound: %lu is beyond the count:%lu!", (unsigned long)i, (unsigned long)self.count);
}
}
@end
NSMutableDictionary
@implementation NSMutableDictionary (Safety)
- (void)key:(id)k value:(id)v {
if (k == nil || ![k conformsToProtocol:@protocol(NSCopying)]) {
Log(@"NSMutableDictionary setObject:forKey:, key %@ is invalid!", k);
return;
}
self[k] = v;
}
@end