Skip to content

Latest commit

 

History

History
386 lines (317 loc) · 12.1 KB

避免崩溃.md

File metadata and controls

386 lines (317 loc) · 12.1 KB

避免崩溃

1 常见闪退

  • 数组初始化带空值
  • 数组插入空值
  • 数组越界(存、取、删除、交换等)
  • 字典初始化带空值
  • 字典设置value为空值
  • 子线程UI操作
  • 字符串范围越界
  • 消息未找到对应方法
  • KVC 未找到对应的key 或插入了空值、字典操作不匹配
  • KVO 未实现监听、add后未remove、多次remove

2 hook系统方法

2.1 数组的hook方法

通过hook关键的带元素下标的方法,判断是否越界。

hook带参数的初始化方法,判断是否存在空值。

同时因为NSArray是类蔟模式,类蔟是抽象工厂模式在iOS下的一种实现方式。包括NSArray、NSDictionary、NSNumber、NSString等。用户使用抽象层的简单接口来调用,但实际上内部已经实现了很多具体的类。

通过Xcode调试可以打印出具体实现的类。

比如NSArray的具体类有NSArrayI、NSArray0、NSArrayM、__NSPlaceholderArray

代码如下:

// 以下交换的方法都封装成类方法便于调用
+ (void)swizzleInstanceMethodWithSelector:(SEL)selector1 AndSelector:(SEL)selector2 {
    Method originMethod = class_getInstanceMethod(self, selector1);
    Method newMethod = class_getInstanceMethod(self, selector2);
    method_exchangeImplementations(originMethod, newMethod);
}

+ (void)swizzleClassMethodWithSelector:(SEL)selector1 AndSelector:(SEL)selector2 {
    Method originMethod = class_getClassMethod(self, selector1);
    Method newMethod = class_getClassMethod(self, selector2);
    method_exchangeImplementations(originMethod, newMethod);
}

// hook 初始化方法
Class class = NSClassFromString(@"__NSPlaceholderArray");
[class swizzleInstanceMethodWithSelector:@selector(initWithObjects:count:) AndSelector:@selector(ch_initWithObjects:count:)];
    
- (instancetype)ch_initWithObjects:(const id _Nonnull [_Nullable])objects count:(NSUInteger)cnt {
    id nObjects[cnt];
    int i = 0;
    for (; i < cnt; i++) {
        if (objects[i]) {
            nObjects[i] = objects[i];
        }
        else {
        	  // 插入空值
            nObjects[i] = NSNull.null;
        }
    }
    return [self ch_initWithObjects:nObjects count:i];
}

// hook 不同模式下的array的取下标方法
Class class = NSClassFromString(@"__NSArrayI");
[class swizzleInstanceMethodWithSelector:@selector(objectAtIndex:) AndSelector:@selector(ch_objectAtIndex:)];
[class swizzleInstanceMethodWithSelector:@selector(objectAtIndexedSubscript:) AndSelector:@selector(ch_objectAtIndexedSubscript:)];
    
class = NSClassFromString(@"__NSArray0");
[class swizzleInstanceMethodWithSelector:@selector(objectAtIndex:) AndSelector:@selector(ch0_objectAtIndex:)];
[class swizzleInstanceMethodWithSelector:@selector(objectAtIndexedSubscript:) AndSelector:@selector(ch0_objectAtIndexedSubscript:)];
    
// 可变数组
class = NSClassFromString(@"__NSArrayM");
[class swizzleInstanceMethodWithSelector:@selector(objectAtIndex:) AndSelector:@selector(chM_objectAtIndex:)];
[class swizzleInstanceMethodWithSelector:@selector(objectAtIndexedSubscript:) AndSelector:@selector(chM_objectAtIndexedSubscript:)];
[class swizzleInstanceMethodWithSelector:@selector(addObject:) AndSelector:@selector(ch_addObject:)];
[class swizzleInstanceMethodWithSelector:@selector(insertObject:atIndex:) AndSelector:@selector(ch_insertObject:atIndex:)];
[class swizzleInstanceMethodWithSelector:@selector(removeObjectAtIndex:) AndSelector:@selector(ch_removeObjectAtIndex:)];
[class swizzleInstanceMethodWithSelector:@selector(replaceObjectAtIndex:withObject:) AndSelector:@selector(ch_replaceObjectAtIndex:withObject:)];
[class swizzleInstanceMethodWithSelector:@selector(setObject:atIndexedSubscript:) AndSelector:@selector(ch_setObject:atIndexedSubscript:)];

// 交换的实现方法
- (id)ch_objectAtIndex:(NSUInteger)idx {
    if (idx < self.count) {
        return [self ch_objectAtIndex:idx];
    }
    else {
        return nil;
    }
}

- (id)ch_objectAtIndexedSubscript:(NSUInteger)idx {
    if (idx < self.count) {
        return [self ch_objectAtIndexedSubscript:idx];
    }
    else {
        return nil;
    }
}

- (id)ch0_objectAtIndex:(NSUInteger)idx {
    if (idx < self.count) {
        return [self ch0_objectAtIndex:idx];
    }
    else {
        
        return nil;
    }
}

- (id)ch0_objectAtIndexedSubscript:(NSUInteger)idx {
    if (idx < self.count) {
        return [self ch0_objectAtIndexedSubscript:idx];
    }
    else {
        
        return nil;
    }
}

- (id)chM_objectAtIndex:(NSUInteger)idx {
    if (idx < self.count) {
        return [self chM_objectAtIndex:idx];
    }
    else {
        
        return nil;
    }
}

- (id)chM_objectAtIndexedSubscript:(NSUInteger)idx {
    if (idx < self.count) {
        return [self chM_objectAtIndexedSubscript:idx];
    }
    else {
        
        return nil;
    }
}

- (void)ch_addObject:(id)anObject {
    if (anObject) {
        return [self ch_addObject:anObject];
    }
    else {
       
    }
}

- (void)ch_insertObject:(id)anObject atIndex:(NSUInteger)index {
    if (anObject && index <= self.count) {
        return [self ch_insertObject:anObject atIndex:index];
    }
    else {
       
    }
}

- (void)ch_removeObjectAtIndex:(NSUInteger)index {
    if (index < self.count) {
        return [self ch_removeObjectAtIndex:index];
    }
    else {
        
    }
}

- (void)ch_replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject {
    if (anObject && index < self.count) {
        return [self ch_replaceObjectAtIndex:index withObject:anObject];
    }
    else {
        
    }
}

- (void)ch_setObject:(id)obj atIndexedSubscript:(NSUInteger)idx {
    if (obj && idx <= self.count) {
        return [self ch_setObject:obj atIndexedSubscript:idx];
    }
    else {
        
    }
}

2.2 字典的hook方法

同理,先找出字典的具体类,然后交换其初始化、设置值的方法。

Class class = NSClassFromString(@"__NSPlaceholderDictionary");
[class swizzleInstanceMethodWithSelector:@selector(initWithObjects:forKeys:count:) AndSelector:@selector(ch_initWithObjects:forKeys:count:)];
    
class = NSClassFromString(@"__NSDictionaryM");
[class swizzleInstanceMethodWithSelector:@selector(setObject:forKey:) AndSelector:@selector(ch_setObject:forKey:)];
[class swizzleInstanceMethodWithSelector:@selector(setObject:forKeyedSubscript:) AndSelector:@selector(ch_setObject:forKeyedSubscript:)];
    
    
- (instancetype)ch_initWithObjects:(id  _Nonnull const [])objects forKeys:(id<NSCopying>  _Nonnull const [])keys count:(NSUInteger)cnt {
    id nObjects[cnt];
    id nKeys[cnt];
    int i = 0;
    for (; i < cnt; i++) {
        if (keys[i]) {
            nKeys[i] = keys[i];
        }
        else {
            nKeys[i] = @"";
#if CRASH_HOOK_ALERT
            
#endif
        }
        if (objects[i]) {
            nObjects[i] = objects[i];
        }
        else {
            nObjects[i] = NSNull.null;
#if CRASH_HOOK_ALERT
            
#endif
        }
    }
    
    return [self ch_initWithObjects:nObjects forKeys:nKeys count:i];
}

- (void)ch_setObject:(id)anObject forKey:(id)aKey {
    if (anObject && aKey) {
        [self ch_setObject:anObject forKey:aKey];
    }
    else {
        
    }
}

- (void)ch_setObject:(nullable id)obj forKeyedSubscript:(id)key {
    if (key) {
        [self ch_setObject:obj forKeyedSubscript:key];
    }
    else {
        
    }
}

2.3 View的hook方法

主要是针对几个显示布局的代码在主线程中被执行。

[self swizzleInstanceMethodWithSelector:@selector(setNeedsLayout) AndSelector:@selector(ch_setNeedsLayout)];
[self swizzleInstanceMethodWithSelector:@selector(setNeedsDisplay) AndSelector:@selector(ch_setNeedsDisplay)];
[self swizzleInstanceMethodWithSelector:@selector(setNeedsDisplayInRect:) AndSelector:@selector(ch_setNeedsDisplayInRect:)];
    
 - (void)ch_setNeedsLayout {
    if (![NSThread isMainThread]) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self ch_setNeedsLayout];
            [self showAlertViewWithMethodName:NSStringFromSelector(_cmd)];
        });
    }
    else {
        [self ch_setNeedsLayout];
    }
}

- (void)ch_setNeedsDisplay {
    if (![NSThread isMainThread]) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self ch_setNeedsDisplay];
            [self showAlertViewWithMethodName:NSStringFromSelector(_cmd)];
        });
    }
    else {
        [self ch_setNeedsDisplay];
    }
}

- (void)ch_setNeedsDisplayInRect:(CGRect)rect {
    if (![NSThread isMainThread]) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self ch_setNeedsDisplayInRect:rect];
            [self showAlertViewWithMethodName:NSStringFromSelector(_cmd)];
        });
    }
    else {
        [self ch_setNeedsDisplayInRect:rect];
    }
}   

2.4 NSString的hook方法

主要是针对字符串的范围操作,避免越界。

Class class = NSClassFromString(@"__NSCFConstantString");
[class swizzleInstanceMethodWithSelector:@selector(substringFromIndex:) AndSelector:@selector(ch_cfConstant_substringFromIndex:)];
[class swizzleInstanceMethodWithSelector:@selector(substringToIndex:) AndSelector:@selector(ch_cfConstant_substringToIndex:)];
[class swizzleInstanceMethodWithSelector:@selector(substringWithRange:) AndSelector:@selector(ch_cfConstant_substringWithRange:)];
    
class = NSClassFromString(@"NSTaggedPointerString");
[class swizzleInstanceMethodWithSelector:@selector(substringFromIndex:) AndSelector:@selector(ch_taggedPointer_substringFromIndex:)];
[class swizzleInstanceMethodWithSelector:@selector(substringToIndex:) AndSelector:@selector(ch_taggedPointer_substringToIndex:)];
[class swizzleInstanceMethodWithSelector:@selector(substringWithRange:) AndSelector:@selector(ch_taggedPointer_substringWithRange:)];
    
- (NSString *)ch_cfConstant_substringFromIndex:(NSUInteger)from {
    if (from > self.length) {
        from = self.length;
        [self showAlertViewWithMethodName:NSStringFromSelector(_cmd)];
    }
    return [self ch_cfConstant_substringFromIndex:from];
}
- (NSString *)ch_cfConstant_substringToIndex:(NSUInteger)to {
    if (to > self.length) {
        to = self.length;
        [self showAlertViewWithMethodName:NSStringFromSelector(_cmd)];
    }
    return [self ch_cfConstant_substringToIndex:to];
}

- (NSString *)ch_cfConstant_substringWithRange:(NSRange)range {
    BOOL isRangeModify = NO;
    if (range.length > self.length) {
        range.length = self.length;
        isRangeModify = YES;
    }
    if (range.location > self.length) {
        range.location = self.length;
        isRangeModify = YES;
    }
    if (range.location + range.length > self.length) {
        range.length = self.length - range.location;
        isRangeModify = YES;
    }
    if (isRangeModify) {
        [self showAlertViewWithMethodName:NSStringFromSelector(_cmd)];
    }
    return [self ch_cfConstant_substringWithRange:range];
}

- (NSString *)ch_taggedPointer_substringFromIndex:(NSUInteger)from {
    if (from > self.length) {
        from = self.length;
        [self showAlertViewWithMethodName:NSStringFromSelector(_cmd)];
    }
    return [self ch_taggedPointer_substringFromIndex:from];
}
- (NSString *)ch_taggedPointer_substringToIndex:(NSUInteger)to {
    if (to > self.length) {
        to = self.length;
        [self showAlertViewWithMethodName:NSStringFromSelector(_cmd)];
    }
    return [self ch_taggedPointer_substringToIndex:to];
}

- (NSString *)ch_taggedPointer_substringWithRange:(NSRange)range {
    BOOL isRangeModify = NO;
    if (range.length > self.length) {
        range.length = self.length;
        isRangeModify = YES;
    }
    if (range.location > self.length) {
        range.location = self.length;
        isRangeModify = YES;
    }
    if (range.location + range.length > self.length) {
        range.length = self.length - range.location;
        isRangeModify = YES;
    }
    if (isRangeModify) {
        [self showAlertViewWithMethodName:NSStringFromSelector(_cmd)];
    }
    return [self ch_taggedPointer_substringWithRange:range];
}

针对系统的方法,我们可以通过hook的方式去避免一些崩溃,主要是找到其中具体的类以及具体的方法。

同时还是有许多场景未能覆盖到,在平时开发中也需要多多留意各种崩溃的情况。