崩溃日志
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 1. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (3), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
简单说明一下这个崩溃造成的原因有两种:
1、刷新section
2、move section
刷新section
UITableView调用刷新某一section,如下:
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
而在刷新某一section的时候,其他的seciton中对应的row个数改变,从而导致崩溃。
下面可以附上一个简单的栗子,一个类搞定的事就不给demo了哈:
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSMutableArray *dataArray;
@property (nonatomic, strong) NSMutableArray *dataArray2;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.dataArray = [NSMutableArray arrayWithObjects:@1,@2,@3,nil];
self.dataArray2 = [NSMutableArray arrayWithObjects:@1,@2,@3,nil];
self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 300, 400)];
self.tableView.backgroundColor = [UIColor redColor];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.view addSubview:self.tableView];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.dataArray2 removeObjectAtIndex:0];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
});
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 30;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 0) {
return self.dataArray.count;
}
return self.dataArray2.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *testCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"~~~~~UITableViewCell"];
testCell.backgroundColor = [UIColor blueColor];
return testCell;
}
@end
move cell
这边主要是在UITableView进行move section
的时候没有注意就会造成该崩溃。简单说明一下UITableView 的move sectionnnn
操作,由于move的时候没有进行刷新会导致一个问题:section对应的indexPath还是原本的值,UITableView没有更新该值,所以需要自己去记录indexPath的值。而在move的时候indexPath的section其中一个值没有更新自己记录的值,多次move的时候就会造成该崩溃。
附上简单的源码:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
static NSString *headerID = @"headerID";
ZTGClassSortHeaderView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:headerID];
if (!headerView) {
headerView = [[ZTGClassSortHeaderView alloc] initWithReuseIdentifier:headerID];
}
ZTGResponseGradeListSortEnvelop *gradeEnvelop = [self.sortArray zt_safeObjectAtIndex:section];
ZTGClassSortHeaderItem *haderItem = [[ZTGClassSortHeaderItem alloc] init];
haderItem.gradeString = gradeEnvelop.gradeName;
haderItem.showUp = section == 0 ? NO : YES;
haderItem.showDown = section == self.sortArray.count - 1 ? NO : YES;
haderItem.section = section;
headerView.headerItem = haderItem;
@weakify(self);
haderItem.didSelectedUp = ^(NSInteger indexSection) {
@strongify(self);
NSInteger toSection = indexSection - 1;
if (toSection >= 0 && toSection < self.sortArray.count) {
[self moveSectionFrom:indexSection toSection:toSection];
}
};
haderItem.didSelectedDown = ^(NSInteger indexSection) {
@strongify(self);
NSInteger toSection = indexSection + 1;
if (toSection < self.sortArray.count && toSection >= 0) {
[self moveSectionFrom:indexSection toSection:toSection];
}
};
return headerView;
}
- (void)moveSectionFrom:(NSInteger)section toSection:(NSInteger)toSection {
self.sortChange = YES;
// 更改数据
[self.sortArray exchangeObjectAtIndex:section withObjectAtIndex:toSection];
// 移动section
[self.sortTableView moveSection:section toSection:toSection];
// 刷新section
ZTGClassSortHeaderView *headerView = (ZTGClassSortHeaderView *)[self.sortTableView headerViewForSection:section];
[self updateSectionStateWithHeader:headerView section:section];
ZTGClassSortHeaderView *toView = (ZTGClassSortHeaderView *)[self.sortTableView headerViewForSection:toSection];
// 此处注释后会出现该崩溃,因为对应的section没有更新
//[self updateSectionStateWithHeader:toView section:toSection];
}
- (void)updateSectionStateWithHeader:(ZTGClassSortHeaderView *)headerView section:(NSInteger)section {
if (headerView.headerItem.updateHeaderState) {
BOOL isUp = section == 0 ? NO : YES;
BOOL isDown = section == self.sortArray.count - 1 ? NO : YES;
headerView.headerItem.updateHeaderState(isUp, isDown, section);
}
}
在headerView.headerItem.updateHeaderState(isUp, isDown, section);
目标section不做更新,再次移动时就会出现改崩溃。
上面的源码是tableView section的一部分操作,具体的可以自己写个简单的demo。
解决
针对以上的崩溃就只能是检查源码了,一般而言UITableView哪里增、删section(或row)
就需要对其进行刷新,如果操作上都是在主线程的话一般而言都是不出会有问题的,因为主线程上是串行。而在异步的时候增、删section(或row)
的时候就需要注意了。