iOS 数组越界 Crash处理经验

摘要:
@MethodswizzleMethod:withMethod:error:@abstract替换实例方法@paramoldSelector要替换的方法@paramnewSelector实际替换为@paramerror的方法替换过程中出现的错误为nil*/+swizzleMethod:originalSelectorwithMethod:swizzledSelectorerror:error@end#import“NSObject+Util.h”#import@implementationNSObject+swizzleMethod:originalSelectorwithMethod:swizzledSelectorerror:error{MethodoriginalMethod=class_getInstanceMethod;if(!

我们先来看看有可能会出现的数组越界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;解决这个问题也很简单代码如下:

- (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 

@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]' 错误了

免责声明:文章转载自《iOS 数组越界 Crash处理经验》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇ProxyConfig属性详解跳转页面的三种方式下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

解决UItableView cell的间隔线 separatorStyle ( plain group 两种类型)

一 . Plain类型的: 做法 cell 的backgroupView 是设置的 57 pix,在此基础上 在方法willDisplayCell 中 cell.backgroundView添加一个高为55Pix的view 作为背景 这样就有了 2pix的 cell 下边框了   -(CGFloat)tableView:(UITableView *)tab...

ios tableview 滑动到底部

tableview滑动到底部,根据页面不同 可以有两种方法 第一种: 一般样式的tableview 没有头和尾的 #pragma mark - 滑到最底部 - (void)scrollTableToFoot:(BOOL)animated { NSInteger s = [self.tableView numberOfSections]; //有多少组 i...

CoreData使用方法二:NSFetchedResultsController实例操作与解说

        学习了NSFetchedResultsController。才深深的体会到coredata的牛逼之处。原来Apple公司弄个新技术。不是平白无故的去弄,会给代码执行到来非常大的优点。coredata不仅能让我们大大的降低代码量。还最大化的提高执行效率。        就拿NSFetchedResultsController来说吧,他是和...

iOS-高仿通讯录之商品索引排序搜索

概述 TableView添加右侧索引, 将数据按照索引分组排序, 并添加搜索功能且在搜索界面复用当前页面. 详细 代码下载:http://www.demodashi.com/demo/10696.html 项目中像一些商品搜索类界面, TableView添加右侧索引的使用越来越多, 的确用户体验提高了许多. 一、主要思路 大致思路: 1....

TableView之表头、表尾,区头、区尾!

一、UITableView的UITableViewStyle   样式分为UITableViewStylePlain和UITableViewStyleGrouped两种;   plain样式下区头和区尾是悬浮的(即拖动表的时候区头和区尾不会消失,一直显示在界面上);   grouped样式区头和区尾是随表一起滑动的。静态的tableview需要分区时(XI...

tableView左滑删除功能

实现三个代理方法即可 -(NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath { return @"删除"; } -(UITableViewCellEditi...