From 36de258ff96c1dc5f5546b292a506345468ef03c Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 23 Mar 2017 11:14:05 +0000 Subject: [PATCH] Avoid multiple initial data loads being issued by UICollectionView/UITableView (#3208) * Avoid multiple initial data loads being issued by UICollectionView/UITableView - Currently, there is a gap between the moment UICollectionView/UITableView triggers its first data load and when ASDataController finishes processing it. During this gap, the view keeps issuing "initial" loads by calling reloadData and causes its data controller to reload multiple times. - Fix by immediately forward the first reloadData call to UICollectionView/UITableView before letting its data controller to handle it for real. During the gap, the view thinks that it loaded initial data but is empty, and thus stops triggering initial loads. Once the data controller finished loading, it will call another reloadData on the view which causes the view to swap to a correct state. * Fix tests * Use the existing flag of ASDataController * Explain unit test --- Source/ASCollectionView.mm | 6 ++++++ Source/ASTableView.mm | 5 +++++ Tests/ASTableViewTests.mm | 7 ++++--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index d1f626c021..d8a2b33dc9 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -317,6 +317,12 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; { ASDisplayNodeAssertMainThread(); + if (! _dataController.initialReloadDataHasBeenCalled) { + // If this is the first reload, forward to super immediately to prevent it from triggering more "initial" loads while our data controller is working. + _superIsPendingDataLoad = YES; + [super reloadData]; + } + void (^batchUpdatesCompletion)(BOOL); if (completion) { batchUpdatesCompletion = ^(BOOL) { diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 6aaac9e543..d1c4558596 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -483,6 +483,11 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; { ASDisplayNodeAssertMainThread(); + if (! _dataController.initialReloadDataHasBeenCalled) { + // If this is the first reload, forward to super immediately to prevent it from triggering more "initial" loads while our data controller is working. + [super reloadData]; + } + void (^batchUpdatesCompletion)(BOOL); if (completion) { batchUpdatesCompletion = ^(BOOL) { diff --git a/Tests/ASTableViewTests.mm b/Tests/ASTableViewTests.mm index 8aaf9e8515..1190a9e780 100644 --- a/Tests/ASTableViewTests.mm +++ b/Tests/ASTableViewTests.mm @@ -606,9 +606,10 @@ [node waitUntilAllUpdatesAreCommitted]; XCTAssertGreaterThan(node.view.numberOfSections, 0); - // Assert that the beginning of the call pattern is correct. - // There is currently noise that comes after that we will allow for this test. - NSArray *expectedSelectors = @[ NSStringFromSelector(@selector(reloadData)) ]; + // The first reloadData call helps prevent UITableView from calling it multiple times while ASDataController is working. + // The second reloadData call is the real one. + NSArray *expectedSelectors = @[ NSStringFromSelector(@selector(reloadData)), + NSStringFromSelector(@selector(reloadData)) ]; XCTAssertEqualObjects(selectors, expectedSelectors); [UITableView deswizzleAllInstanceMethods];