小心UITableView中的"数组越界Crash&q
使用UITableView的reloadData时候,有时候会遇到数据越界的Crash,其根源在于:
使用reloadData刷新UITableView时候,UITableViewDataSource和UITableViewDelegate的回调方法中,有些是同步发生的,有些则是异步发生的(如:cellForRowAtIndexPath是异步执行的)。
例1:同步的回调:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 44;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.dataSource.count;
}
例2:异步的回调:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath )indexPath{
//... NSString content = self.dataSource[indexPath.row];
//...
}
总结:如果在异步执行cellForRowAtIndexPath时候,self.dataSource发生了变化(如删除某一个元素),极有可能发生数组越界的异常。因为reloadData返回之后,下一次loop就开始执行异步的操作了。在多线程场景下,列表界面的数据有可能经常变化,就可能会出现偶现的bug了;当列表界面数据不怎么变化的时候,几乎感知不到这种异常的存在。
目前的处理办法是:因为dataSource类型是NSMutableArray,为NSMutableArray提供一个safeObjectAtIndex方法,这个方法放到NSMutableArray的分类中。
static NSString * const kCellReuseIdentifier = @"kCellReuseIdentifier";
// ...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellReuseIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellReuseIdentifier];
}
NSString *content = [self.dataSource safeObjectAtIndex:indexPath.row];
cell.textLabel.text = content;
cell.backgroundColor = [UIColor redColor];
return cell;
}
NSMutableArray的safeObjectAtIndex声明和实现如下:
//NSMutableArray+Safe.h
@interface NSMutableArray (Safe)
- (id)safeObjectAtIndex:(NSUInteger)index;
@end
//NSMutableArray+Safe.m
#import "NSMutableArray+Safe.h"
@implementation NSMutableArray (Safe)
- (id)safeObjectAtIndex:(NSUInteger)index{
if (index < self.count){
return [self objectAtIndex:index];
}else{
NSLog(@"警告:数组越界!!!");
}
return nil;
}
小尾巴:我们为什么不使用runtime的特性,混写NSMutableArray的objectAtIndex
参考链接:危险的UITableView