iOS 数组越界 Crash处理经验

来源:互联网 发布:php注册登录系统 编辑:程序博客网 时间:2024/06/12 21:06

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

[objc] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {  
  2.     WelfareItem *item = [_datasourceArray objectAtIndex:indexPath.row];//有可能会越界,你在下拉刷新时会用[_datasourceArray removeAllObjects],这时你又点了某个cell就会Crash  
  3. }  
  4.   
  5. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {  
  6.     WelfareItem *item = _datasourceArray[indexPath.row];//有可能会越界,两个地方用了[tableView reloadData];后一个有[_datasourceArray removeAllObjects];前一个还没有执行完,就会Crash  
  7. }  

上面代码是有可能会越界的;出现Crash也不好复现,发出去的App总是能收到几条Crash;解决这个问题也很简单代码如下:

[objc] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {  
  2.     WelfareItem *item = nil;  
  3.     if (indexPath.row < [_datasourceArray count]) {//无论你武功有多高,有时也会忘记加  
  4.         item = [_datasourceArray objectAtIndex:indexPath.row];  
  5.     }  
  6. }  
  7.   
  8. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {  
  9.     WelfareItem *item = nil;  
  10.     if (indexPath.row < [_datasourceArray count]) {  
  11.         item = [_datasourceArray objectAtIndex:indexPath.row];  
  12.     }  
  13. }  

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

[objc] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /*! 
  2.  @category  
  3.  @abstract NSObject的Category 
  4.  */  
  5. @interface NSObject (Util)  
  6.   
  7. /*! 
  8. @method swizzleMethod:withMethod:error: 
  9. @abstract 对实例方法进行替换 
  10. @param oldSelector 想要替换的方法 
  11. @param newSelector 实际替换为的方法 
  12. @param error 替换过程中出现的错误,如果没有错误为nil 
  13. */  
  14. + (BOOL)swizzleMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector error:(NSError **)error;  
  15.   
  16. @end  
  17.   
  18.   
  19. #import "NSObject+Util.h"  
  20. #import <objc/runtime.h>  
  21.   
  22. @implementation NSObject (Util)  
  23.   
  24. + (BOOL)swizzleMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector error:(NSError **)error  
  25. {  
  26.     Method originalMethod = class_getInstanceMethod(self, originalSelector);  
  27.     if (!originalMethod) {  
  28.         NSString *string = [NSString stringWithFormat:@" %@ 类没有找到 %@ 方法",NSStringFromClass([self class]),NSStringFromSelector(originalSelector)];  
  29.         *error = [NSError errorWithDomain:@"NSCocoaErrorDomain" code:-1 userInfo:[NSDictionary dictionaryWithObject:string forKey:NSLocalizedDescriptionKey]];  
  30.         return NO;  
  31.     }  
  32.       
  33.     Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);  
  34.     if (!swizzledMethod) {  
  35.         NSString *string = [NSString stringWithFormat:@" %@ 类没有找到 %@ 方法",NSStringFromClass([self class]),NSStringFromSelector(swizzledSelector)];  
  36.         *error = [NSError errorWithDomain:@"NSCocoaErrorDomain" code:-1 userInfo:[NSDictionary dictionaryWithObject:string forKey:NSLocalizedDescriptionKey]];  
  37.         return NO;  
  38.     }  
  39.       
  40.     if (class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {  
  41.         class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));  
  42.     }  
  43.     else {  
  44.         method_exchangeImplementations(originalMethod, swizzledMethod);  
  45.     }  
  46.       
  47.     return YES;  
  48. }  
  49.   
  50. @end  
  51.   
  52.   
  53. @implementation NSArray (ErrerManager)  
  54.   
  55. + (void)load  
  56. {  
  57.     static dispatch_once_t onceToken;  
  58.     dispatch_once(&onceToken, ^{  
  59.         @autoreleasepool  
  60.         {  
  61.             [objc_getClass("__NSArrayI") swizzleMethod:@selector(objectAtIndex:) withMethod:@selector(swizzleObjectAtIndex:) error:nil];  
  62.             [objc_getClass("__NSArrayM") swizzleMethod:@selector(objectAtIndex:) withMethod:@selector(swizzleObjectAtIndex:) error:nil];  
  63.         };  
  64.     });  
  65. }  
  66.   
  67. - (id)swizzleObjectAtIndex:(NSUInteger)index  
  68. {  
  69.     if (index < self.count)  
  70.     {  
  71.         return [self swizzleObjectAtIndex:index];  
  72.     }  
  73.     NSLog(@"%@ 越界",self);  
  74.     return nil;//越界返回为nil  
  75. }  
  76.   
  77. @end  

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

[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. 1 tbreader 0x002b93e9 tbreader + 2098153  
  2. 2 libsystem_platform.dylib 0x33a66873 _sigtramp + 34  
  3. 3 libsystem_blocks.dylib 0x33941ae1 _Block_release + 216  
  4. 4 libobjc.A.dylib 0x333c11a9 + 404  
  5. 5 CoreFoundation 0x25ba23a9 _CFAutoreleasePoolPop + 16  
  6. 6 UIKit 0x2912317f + 42  
  7. 7 CoreFoundation 0x25c565cd + 20  
  8. 8 CoreFoundation 0x25c53c8b + 278  
  9. 9 CoreFoundation 0x25c54093 + 914  
  10. 10 CoreFoundation 0x25ba2621 CFRunLoopRunSpecific + 476  
  11. 11 CoreFoundation 0x25ba2433 CFRunLoopRunInMode + 106  
  12. 12 GraphicsServices 0x2cf0a0a9 GSEventRunModal + 136  
  13. 13 UIKit 0x2918c809 UIApplicationMain + 1440  
都是这个Crash,出现在iOS7以上(含iOS7),关键还没有用户反馈有问题,Crash高了几倍没有一个用户反馈这种情况还是少见的,大家测试还复现不了;测试了一周终于复现了一样的Crash;是这样出现的,替换了objectAtIndex方法有输入的地方出来了软键盘按手机Home键就Crash了;此法不行只,只能另寻他策了。后来我们就给数组新增扩展方法代码如下

[objc] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. @interface NSArray (SHYUtil)  
  2.   
  3. /*! 
  4.  @method objectAtIndexCheck: 
  5.  @abstract 检查是否越界和NSNull如果是返回nil 
  6.  @result 返回对象 
  7.  */  
  8. - (id)objectAtIndexCheck:(NSUInteger)index;  
  9.   
  10. @end  
  11.   
  12.   
  13. #import "NSArray+SHYUtil.h"  
  14.   
  15. @implementation NSArray (SHYUtil)  
  16.   
  17. - (id)objectAtIndexCheck:(NSUInteger)index  
  18. {  
  19.     if (index >= [self count]) {  
  20.         return nil;  
  21.     }  
  22.       
  23.     id value = [self objectAtIndex:index];  
  24.     if (value == [NSNull null]) {  
  25.         return nil;  
  26.     }  
  27.     return value;  
  28. }  
  29.   
  30. @end  
把之前的代码 WelfareItem *item = [_datasourceArray objectAtIndex:indexPath.row] 改为 WelfareItem *item = [_datasourceArray objectAtIndexCheck:indexPath.row] 就可以了。这样就可以彻底解决数组越界 -[__NSArrayI objectAtIndex:]: index 100 beyond bounds [0 .. 1]' 错误了
0 0