OC防犯越界崩溃策略整理

来源:互联网 发布:centos创建文件夹命令 编辑:程序博客网 时间:2024/05/21 02:52

写在前面

OC上常见崩溃一般不亚于 数组越界以及字典设置为nil。

虽然大家基本都知道这些情况下,程序会表示抱歉,我要崩溃的,但是大多数情况下传入进来的都是一个变量,变量真正的值有时候就会正在的出乎程序员的意料,比如过大导致越界,或是尽然是空的。如果说这种意外情况无法避免,那么只能从侧面采取保护措施。

我先整理下常见的崩溃方法:

NSArray:    objectAtIndex:NSMutableArray:    addObject:    insertObject:atIndex:    removeObjectAtIndex:    replaceObjectAtIndex:withObject:NSMutableDictionary:    setObject:forKey:
  1. 取值:index超出array的索引范围
  2. 添加:插入的object为nil或者Null
  3. 插入:index大于count、插入的object为nil或者Null
  4. 删除:index超出array的索引范围
  5. 替换: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
原创粉丝点击