UITableView、UICollectionView之vis

2018-11-05  本文已影响140人  吕木

前言:

对于[UITableView visibleCells]的正确获取,相信很多人,都会采用切换主队列的方式,来保证visibleCells数据的正确。究其原因,和RealoadData方法的异步性和RunLoop机制有关。

那么在当前以iOS8+环境下,切换主队列的方式,是否还生效呢?请大家随本文一起探究下去。

[self.tableView reloadData];
// 切换到主队列
dispatch_async(dispatch_get_main_queue(), ^{
    NSArray *array = [self.tableView visibleCells];
    NSLog(@"visibleCells====:%@",@(array.count));
});

UITableView

首先确认ReloadData的异步性。执行测试代码:

- (void)testTableView {
    [self.tableView reloadData];
    NSLog(@"reloadData 执行完成");
}

监听reloadData执行前后,并同时监听RunLoop时机:

16:45:08 被唤醒
16:45:08 即将处理Timer事件
16:45:08 即将处理Source事件
16:45:08 -[ViewController btn:]
16:32:54 -[TestTableView reloadData] -----
16:45:08 -[ViewController numberOfSectionsInTableView:]
16:32:54 -[TestTableView setNeedsLayout] -----
16:32:54 -[TestTableView setNeedsLayout] =====
16:45:08 -[ViewController tableView:numberOfRowsInSection:]
16:32:54 -[TestTableView setNeedsLayout] -----
16:32:54 -[TestTableView setNeedsLayout] =====
16:32:54 -[TestTableView setNeedsLayout] -----
16:32:54 -[TestTableView setNeedsLayout] =====
16:32:54 -[TestTableView setNeedsLayout] -----
16:32:54 -[TestTableView setNeedsLayout] =====
16:32:54 -[TestTableView reloadData] =====
16:45:08 reloadData 执行完成
16:45:08 即将处理Timer事件
16:45:08 即将处理Source事件
16:45:08 即将休眠
16:32:54 -[TestTableView layoutSubviews] -----
16:45:08 -[ViewController tableView:cellForRowAtIndexPath:]=====
16:45:08 -[ViewController tableView:heightForRowAtIndexPath:]
16:45:08 -[ViewController tableView:heightForRowAtIndexPath:]
16:32:54 -[TestTableView layoutSubviews] =====
16:45:08 被唤醒
16:45:08 即将处理Timer事件
16:45:08 即将处理Source事件
16:45:08 即将休眠

分析日志:

reloadData 方法内部,执行了高度计算,但没有进行Cell的渲染工作。而是调用了setNeedsLayout,并将在下一个RunLoop处理时机,调用[UITableView layoutSubviews]方法对cell们进行渲染。

由此可见,reloadData方法执行完成后,cells 并没有被渲染,此时立即调用visibleCells方法会获取到数据的数据将是错误的。

但真实的情况呢?

测试visibleCells方法如下:

- (void)testTableView {

    [self.tableView reloadData];
    NSLog(@"reloadData 执行完成");

    NSArray *array = [self.tableView visibleCells];
    NSLog(@"visibleCells----:%@",@(array.count));

    dispatch_async(dispatch_get_main_queue(), ^{
        NSArray *array = [self.tableView visibleCells];
        NSLog(@"visibleCells==== :%@",@(array.count));
    });
}

同时监听runloop时机,日志如下:

13:00:32 即将处理Timer事件
13:00:32 即将处理Source事件
13:00:32 -[ViewController btn:]
13:00:32 -[TestTableView reloadData] -----
13:00:32 -[ViewController numberOfSectionsInTableView:]
13:00:32 -[ViewController tableView:numberOfRowsInSection:]
13:00:32 -[ViewController tableView:heightForRowAtIndexPath:]
13:00:32 -[ViewController tableView:heightForRowAtIndexPath:]
13:00:32 -[TestTableView reloadData] =====
13:00:32 reloadData 执行完成
13:00:32 -[ViewController tableView:cellForRowAtIndexPath:]
13:00:32 -[ViewController tableView:heightForRowAtIndexPath:]
13:00:32 -[ViewController tableView:cellForRowAtIndexPath:]
13:00:32 -[ViewController tableView:heightForRowAtIndexPath:]
13:00:32 visibleCells----:2
13:00:32 visibleCells====:2
13:00:32 即将处理Timer事件
13:00:32 即将处理Source事件
16:30:14 即将休眠
15:03:46 -[TestTableView layoutSubviews] -----
15:03:46 -[TestTableView layoutSubviews] =====
16:30:14 被唤醒

两次visibleCells方法都返回了正确的cells数据。这是为何?进一步查看visibleCells前后调用堆栈信息:

tableView.visibleCells.jpg
-[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:]

分析上述日志:

原来如此,调用[UITableView visibleCells]方法时,会促使cell进行渲染。

小结

因此,UITableView之visibleCells,如今(iOS8+),可以直接调用,不需要再担心,数据不正确了!

UICollectionView

确认了[UITableView visibleCells]的机制之后,忍不住联想到UICollectionView是不是也这样呢。两者有很多的相似,同样继承自UIScrollView,类似的协议等等,那么直接调用visibleCells是不是也可以了?

Don't talk(bb),show you the code!

测试代码如下:

- (void)testCollectionView {

    [self.collectionView reloadData];
    NSLog(@"[self.collectionView reloadData];");

    NSLog(@"visibleCells---%@",@([self.collectionView visibleCells].count));

    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"visibleCells===%@",@([self.collectionView visibleCells].count));
    });
}
16:13:56 被唤醒
16:13:56 即将处理Timer事件
16:13:56 即将处理Source事件
16:13:56 -[ViewController btn:]
16:15:10 -[TestCollectionView reloadData] -----
16:15:10 -[TestCollectionView reloadData] =====
16:13:56 reloadData 执行完成
16:13:56 visibleCells---0
16:13:56 visibleCells===0
16:13:56 即将处理Timer事件
16:13:56 即将处理Source事件
16:13:56 即将休眠
16:15:10 -[TestCollectionView layoutSubviews] -----
16:15:10 layoutSubviews - visibleCells:3
16:15:10 -[TestCollectionView layoutSubviews] =====
16:13:56 被唤醒

额,两次获取visibleCells数据都是0,两种方式都错了,这让人情何以堪!

注意到,下面代码中,visibleCells 数据获取正确了。

16:15:10 -[TestCollectionView layoutSubviews] -----
16:15:10 layoutSubviews - visibleCells:3
16:15:10 -[TestCollectionView layoutSubviews] =====

这是因为collectionView 和 tableView 在visibleCells内部有所不同。collectionView.visibleCells 方法执行时,没有立即触发layout相关事件。
因此,visibleCells 获取失败。需要在[TestCollectionView layoutSubviews]执行后才获取才能成功。

进一步打印 cellforRow方法的调用栈如下

UICollectionView.loadCells.png

总归是和UITableView有了相似,创建Cell视图前,也执行前缀为createPreparedCell的方法

-[UICollectionView _createPreparedCellForItemAtIndexPath:withLayoutAttributes:applyAttributes:isFocused:notify:]

小结

也就是说,想要立即获取正确的visibleCells,需要主动触发layoutSubviews后,才进行获取。

总结

引申

如果,UICollectionView 作为一个Cell,被加载在UITableView上。此时获取visibleCells会是怎样的?请尝试回答以下代码中的问题。


[self.tableView reloadData];
NSArray *tableViewVisibleCells = [self.tableView visibleCells];
//  问题1:tableViewVisibleCells 是否正确?

for (UITableViewCell *cell in tableViewVisibleCells) {
// cell 上加载内容的视图,对应可能是CollectionView
    UIView *view = cell.realContentView;
    if ([view isKindOfClass:[UICollectionView class]]) {

    UICollectionView *collectionView = (UICollectionView *)view;
    NSArray *array = [collectionView visibleCells];
    // 问题2:array 获取是否正确呢?
    }
}

答案:

你答对了吗?

资料

上一篇 下一篇

猜你喜欢

热点阅读