UITableView、UICollectionView之visibleCells方法的前世今生

前言:

對于[UITableView visibleCells]的正確獲取,相信很多人,都會采用切換主隊列的方式,來保證visibleCells數(shù)據(jù)的正確。究其原因,和RealoadData方法的異步性和RunLoop機制有關(guān)。

那么在當(dāng)前以iOS8+環(huán)境下,切換主隊列的方式,是否還生效呢?請大家隨本文一起探究下去。

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

UITableView

首先確認ReloadData的異步性。執(zhí)行測試代碼:

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

監(jiān)聽reloadData執(zhí)行前后,并同時監(jiān)聽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 執(zhí)行完成
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 方法內(nèi)部,執(zhí)行了高度計算,但沒有進行Cell的渲染工作。而是調(diào)用了setNeedsLayout,并將在下一個RunLoop處理時機,調(diào)用[UITableView layoutSubviews]方法對cell們進行渲染。

由此可見,reloadData方法執(zhí)行完成后,cells 并沒有被渲染,此時立即調(diào)用visibleCells方法會獲取到數(shù)據(jù)的數(shù)據(jù)將是錯誤的。

但真實的情況呢?

測試visibleCells方法如下:

- (void)testTableView {

    [self.tableView reloadData];
    NSLog(@"reloadData 執(zhí)行完成");

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

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

同時監(jiān)聽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 執(zhí)行完成
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數(shù)據(jù)。這是為何?進一步查看visibleCells前后調(diào)用堆棧信息:

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

分析上述日志:

原來如此,調(diào)用[UITableView visibleCells]方法時,會促使cell進行渲染。

小結(jié)

  • reloadData 方法內(nèi)部分為兩部分:
  • 計算了contenSize、contentOffset相關(guān)的內(nèi)容。
  • 設(shè)置當(dāng)前TableView的子視圖需要修改渲染和布局。這些任務(wù)將放在后續(xù)主隊列中。
  • 在下一次RunLoop時機,執(zhí)行l(wèi)ayoutSubviews方法。對TableView的子視圖進行渲染和布局。
  • visibleCells 方法內(nèi)部,會執(zhí)行createPreparedCell方法,使cell內(nèi)容提前渲染。并在渲染過后,返回正確的Cells內(nèi)容。

因此,UITableView之visibleCells,如今(iOS8+),可以直接調(diào)用,不需要再擔(dān)心,數(shù)據(jù)不正確了!

UICollectionView

確認了[UITableView visibleCells]的機制之后,忍不住聯(lián)想到UICollectionView是不是也這樣呢。兩者有很多的相似,同樣繼承自UIScrollView,類似的協(xié)議等等,那么直接調(diào)用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 執(zhí)行完成
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數(shù)據(jù)都是0,兩種方式都錯了,這讓人情何以堪!

注意到,下面代碼中,visibleCells 數(shù)據(jù)獲取正確了。

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

這是因為collectionView 和 tableView 在visibleCells內(nèi)部有所不同。collectionView.visibleCells 方法執(zhí)行時,沒有立即觸發(fā)layout相關(guān)事件。
因此,visibleCells 獲取失敗。需要在[TestCollectionView layoutSubviews]執(zhí)行后才獲取才能成功。

進一步打印 cellforRow方法的調(diào)用棧如下

UICollectionView.loadCells.png

總歸是和UITableView有了相似,創(chuàng)建Cell視圖前,也執(zhí)行前綴為createPreparedCell的方法

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

小結(jié)

也就是說,想要立即獲取正確的visibleCells,需要主動觸發(fā)layoutSubviews后,才進行獲取。

  • reloadData 方法內(nèi)部分為兩部分:
  • 計算了contenSize、contentOffset相關(guān)的內(nèi)容。
  • 設(shè)置當(dāng)前TableView的子視圖需要修改渲染和布局。這些任務(wù)將放在后續(xù)主隊列中。
  • 在下一次RunLoop時機,執(zhí)行l(wèi)ayoutSubviews方法。對UICollectionView的子視圖進行渲染和布局。
  • visibleCells 方法內(nèi)部,不會執(zhí)行createPreparedCell方法,及無法確保cells被正確獲取。
  • 如果需要正確的獲取visibleCells,則需要確保證獲取時機在layoutSubView之后。
  • 譬如,可以主動調(diào)用 [UICollectionView layoutIfNeeded]、[UICollectionView layouSubViews]等

總結(jié)

  • [UITableView visibleCells]方法,內(nèi)部機制,已經(jīng)保證了獲取數(shù)據(jù)的正確性。
  • [UICollectionView visibleCells]方法,想要確保數(shù)據(jù)正確性,需確保layouSubViews事件被提前執(zhí)行。

引申

如果,UICollectionView 作為一個Cell,被加載在UITableView上。此時獲取visibleCells會是怎樣的?請嘗試回答以下代碼中的問題。


[self.tableView reloadData];
NSArray *tableViewVisibleCells = [self.tableView visibleCells];
//  問題1:tableViewVisibleCells 是否正確?

for (UITableViewCell *cell in tableViewVisibleCells) {
// cell 上加載內(nèi)容的視圖,對應(yīng)可能是CollectionView
    UIView *view = cell.realContentView;
    if ([view isKindOfClass:[UICollectionView class]]) {

    UICollectionView *collectionView = (UICollectionView *)view;
    NSArray *array = [collectionView visibleCells];
    // 問題2:array 獲取是否正確呢?
    }
}

答案:

  • 問題1:正確
  • 問題2:正確

你答對了嗎?

資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 30,281評論 8 265
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,681評論 1 32
  • 面向?qū)ο蟮娜筇匦裕悍庋b、繼承、多態(tài) OC內(nèi)存管理 _strong 引用計數(shù)器來控制對象的生命周期。 _weak...
    運氣不夠技術(shù)湊閱讀 1,230評論 0 10
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,653評論 30 472
  • 《暗香》 文 / 文君 月光旖旎。 見曲廊疏影,氣如蘭芷。 料峭春寒,幾許清芬惹人醉。 殘雪西風(fēng)嫉妒,又怎的、任他...
    君無情545閱讀 216評論 2 8

友情鏈接更多精彩內(nèi)容