• iOS 数组越界 Crash加工经验


    我们先来看看有可能会出现的数组越界Crash的地方。

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
        WelfareItem *item = [_datasourceArray objectAtIndex:indexPath.row];//有可能会越界,你在下拉刷新时会用[_datasourceArray removeAllObjects],这时你又点了某个cell就会Crash
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        WelfareItem *item = _datasourceArray[indexPath.row];//有可能会越界,两个地方用了[tableView reloadData]。后一个有[_datasourceArray removeAllObjects]。前一个还没有运行完,就会Crash
    }

    上面代码是有可能会越界的;出现Crash也不好复现。发出去的App总是能收到几条Crash;解决问题也非常easy代码例如以下:

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
        WelfareItem *item = nil;
        if (indexPath.row < [_datasourceArray count]) {//不管你武功有多高,有时也会忘记加
            item = [_datasourceArray objectAtIndex:indexPath.row];
        }
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        WelfareItem *item = nil;
        if (indexPath.row < [_datasourceArray count]) {
            item = [_datasourceArray objectAtIndex:indexPath.row];
        }
    }

    问题又来了,不管你武功有多高。有时也会忘记加。所以我们要想一招制敌办法。我是想到了用Runtime把objectAtIndex方法替换一下。代码例如以下:

    /*!
     @category 
     @abstract NSObject的Category
     */
    @interface NSObject (Util)
    
    /*!
    @method swizzleMethod:withMethod:error:
    @abstract 对实例方法进行替换
    @param oldSelector 想要替换的方法
    @param newSelector 实际替换为的方法
    @param error 替换过程中出现的错误,假设没有错误为nil
    */
    + (BOOL)swizzleMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector error:(NSError **)error;
    
    @end
    
    
    #import "NSObject+Util.h"
    #import <objc/runtime.h>
    
    @implementation NSObject (Util)
    
    + (BOOL)swizzleMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector error:(NSError **)error
    {
        Method originalMethod = class_getInstanceMethod(self, originalSelector);
        if (!originalMethod) {
            NSString *string = [NSString stringWithFormat:@" %@ 类没有找到 %@ 方法",NSStringFromClass([self class]),NSStringFromSelector(originalSelector)];
    		*error = [NSError errorWithDomain:@"NSCocoaErrorDomain" code:-1 userInfo:[NSDictionary dictionaryWithObject:string forKey:NSLocalizedDescriptionKey]];
            return NO;
        }
        
        Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
        if (!swizzledMethod) {
            NSString *string = [NSString stringWithFormat:@" %@ 类没有找到 %@ 方法",NSStringFromClass([self class]),NSStringFromSelector(swizzledSelector)];
    		*error = [NSError errorWithDomain:@"NSCocoaErrorDomain" code:-1 userInfo:[NSDictionary dictionaryWithObject:string forKey:NSLocalizedDescriptionKey]];
            return NO;
        }
        
        if (class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
            class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        }
        else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
        
        return YES;
    }
    
    @end
    
    
    @implementation NSArray (ErrerManager)
    
    + (void)load
    {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            @autoreleasepool
            {
                [objc_getClass("__NSArrayI") swizzleMethod:@selector(objectAtIndex:) withMethod:@selector(swizzleObjectAtIndex:) error:nil];
                [objc_getClass("__NSArrayM") swizzleMethod:@selector(objectAtIndex:) withMethod:@selector(swizzleObjectAtIndex:) error:nil];
            };
        });
    }
    
    - (id)swizzleObjectAtIndex:(NSUInteger)index
    {
        if (index < self.count)
        {
            return [self swizzleObjectAtIndex:index];
        }
        NSLog(@"%@ 越界",self);
        return nil;//越界返回为nil
    }
    
    @end

    有了上面代码我们用 [_datasourceArray objectAtIndex:indexPath.row] 就不会发生越界Crash了。越界
    了会返回nil。看来是一个比較不错的解决方式。把app发出去吧,结果我们Crash比之前高了好几倍(越界的Crash没有了,出新的Crash了)。Crash例如以下

    1 tbreader 0x002b93e9 tbreader + 2098153
    2 libsystem_platform.dylib 0x33a66873 _sigtramp + 34
    3 libsystem_blocks.dylib 0x33941ae1 _Block_release + 216
    4 libobjc.A.dylib 0x333c11a9 + 404
    5 CoreFoundation 0x25ba23a9 _CFAutoreleasePoolPop + 16
    6 UIKit 0x2912317f + 42
    7 CoreFoundation 0x25c565cd + 20
    8 CoreFoundation 0x25c53c8b + 278
    9 CoreFoundation 0x25c54093 + 914
    10 CoreFoundation 0x25ba2621 CFRunLoopRunSpecific + 476
    11 CoreFoundation 0x25ba2433 CFRunLoopRunInMode + 106
    12 GraphicsServices 0x2cf0a0a9 GSEventRunModal + 136
    13 UIKit 0x2918c809 UIApplicationMain + 1440
    都是这个Crash。出如今iOS7以上(含iOS7),关键还没实用户反馈有问题,Crash高了几倍没有一个用户反馈这样的情况还是少见的,大家測试还复现不了;測试了一周最终复现了一样的Crash;是这样出现的。替换了objectAtIndex方法有输入的地方出来了软键盘按手机Home键就Crash了;此法不行仅仅,仅仅能另寻他策了。

    后来我们就给数组新增扩展方法代码例如以下

    @interface NSArray (SHYUtil)
    
    /*!
     @method objectAtIndexCheck:
     @abstract 检查是否越界和NSNull假设是返回nil
     @result 返回对象
     */
    - (id)objectAtIndexCheck:(NSUInteger)index;
    
    @end
    
    
    #import "NSArray+SHYUtil.h"
    
    @implementation NSArray (SHYUtil)
    
    - (id)objectAtIndexCheck:(NSUInteger)index
    {
        if (index >= [self count]) {
            return nil;
        }
        
        id value = [self objectAtIndex:index];
        if (value == [NSNull null]) {
            return nil;
        }
        return value;
    }
    
    @end
    把之前的代码 WelfareItem *item = [_datasourceArray objectAtIndex:indexPath.row] 改为 WelfareItem *item = [_datasourceArray objectAtIndexCheck:indexPath.row] 在上面。因此,有可能解决数组边界 -[__NSArrayI objectAtIndex:]: index 100 beyond bounds [0 .. 1]' 错误

  • 相关阅读:
    JQuery
    Ubuntu18.04
    Ubuntu18.04
    Ubuntu18.04
    Ubuntu18.04
    Cordova
    Android
    Android
    Delphi
    postgresql之 drop & delete & truncate
  • 原文地址:https://www.cnblogs.com/hrhguanli/p/5041214.html
Copyright © 2020-2023  润新知