美文网首页
UITableView 数组越界

UITableView 数组越界

作者: lth123 | 来源:发表于2021-04-13 20:20 被阅读0次

    当我们reloadData的时候,会刷新UITableView,随后会进入一系列UITableViewDataSource和UITableViewDelegate的回调,其中有些是和reloadData同步发生的,有些则是异步发生的。

    - (void)reload{
        NSLog(@"reloadData 之前");
        self.array = @[@"1",@"2"];
        [self.tableview reloadData];
        NSLog(@"reloadData 之后");
    }
    
    
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
        NSLog(@"numberOfSectionsInTableView");
        
        return 1;
    }
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
        NSLog(@"numberOfRowsInSection");
        return self.array.count;
    }
    
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
        NSLog(@"cellForRowAtIndexPath");
        UITableViewCell *cell = [UITableViewCell new];
        cell.textLabel.text = self.array[indexPath.row];
        return cell;;
    }
    
    

    执行reload方法的日志顺序如下:

    reloadData 之前
    numberOfSectionsInTableView
    numberOfRowsInSection
    reloadData 之后
    cellForRowAtIndexPath
    cellForRowAtIndexPath

    从日志顺序可以看出,[self.tableview reloadData]执行时,内部调用了numberOfSectionsInTableView和numberOfRowsInSection两个代理方法;当[self.tableview reloadData]执行完成后,在某个时间调用了cellForRowAtIndexPath代理方法。由此可以得出cellForRowAtIndexPath是异步执行的,当reloadData执行完成后,才会执行cellForRowAtIndexPath。这就是cellForRowAtIndexPath经常出现数组越界的根源。

    在实际业务开发中,当接口返回数据后,会调用tableView的reloadData方法刷新列表,更新数据。当reloadData执行完成后,numberOfSectionsInTableView和numberOfRowsInSection这两个代理方法已经执行完毕,tableView的 section 和row已经确定,如果此时数据array在cellForRowAtIndexPath执行之前被修改了,如果长度比之前小,就会出现数组越界的情况。

    在numberOfRowsInSection打断点,然后执行[self.tableview reloadData],调用堆栈如下图:


    1618314945498.jpg
    • [UITableView reloadData]
    • [UITableView noteNumberOfRowsChanged]
    • [UITableViewRowData numberOfRows]
    • [UISectionRowData refreshWithSection:tableView:tableViewRowData:]
    • [UITableView _numberOfRowsInSection:]
    • numberOfRowsInSection

    结合上面的打印顺序,可以看到当执行[UITableView reloadData]时,会立马调用numberOfRowsInSection更新rows

    在cellForRowAtIndexPath打断点,调用堆栈如下


    1618315324755.jpg
    • [CALayer layoutSublayers]
    • [UIView(CALayerDelegate) layoutSublayersOfLayer:]
    • [UITableView layoutSubviews]
    • [UITableView _updateVisibleCellsNow:]
    • [UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:]
    • cellForRowAtIndexPath

    通过调用堆栈看到,cellForRowAtIndexPath数据源方法是在layoutSublayers后调用的,而layoutSublayers是下一个runloop执行的,所以可以得到cellForRowAtIndexPath是异步执行,并且在下一个runloop中执行。

    如果在reloadData之后删除一个数据,会出现数组越界crash,代码如下

    - (void)reload{
        NSLog(@"reloadData 之前");
        self.array = @[@"1",@"2"];
        [self.tableview reloadData];
        self.array = @[@"1"];
        NSLog(@"reloadData 之后");
    }
    

    [图片上传中...(1618315930883.jpg-b42cf2-1618315949497-0)]

    通过上面的堆栈可以分析出,如果在reloadData之后,立马通知系统调用layoutSublayers,则不会出现数组越界

    • 1.通过layoutIfNeeded,layoutIfNeeded的作用:立即执行layoutSubviews ,在当前函数栈中
    - (void)reload{
        NSLog(@"reloadData 之前");
        self.array = @[@"1",@"2"];
        [self.tableview reloadData];
        [self.view layoutIfNeeded];
        self.array = @[@"1"];
        NSLog(@"reloadData 之后");
    }
    
    1618315930883.jpg

    可以看到在reload中就执行了cellForRowAtIndexPath,不会出现crash。

    • 2.[self.tableview visibleCells] 也可以让cellForRowAtIndexPath在当前函数中执行

    相关文章

      网友评论

          本文标题:UITableView 数组越界

          本文链接:https://www.haomeiwen.com/subject/mniclltx.html