diff --git a/Schemas/configuration.json b/Schemas/configuration.json index 334e8a2fc..2126fa876 100644 --- a/Schemas/configuration.json +++ b/Schemas/configuration.json @@ -19,10 +19,13 @@ "exp_unfair_lock", "exp_infer_layer_defaults", "exp_network_image_queue", - "exp_dealloc_queue_v2", "exp_collection_teardown", "exp_framesetter_cache", - "exp_skip_clear_data" + "exp_skip_clear_data", + "exp_did_enter_preload_skip_asm_layout", + "exp_disable_a11y_cache", + "exp_skip_a11y_wait", + "exp_new_default_cell_layout_mode" ] } } diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 59e4ef4fa..e982f8ebf 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -317,6 +317,12 @@ - (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionV [self _configureCollectionViewLayout:layout]; + if (ASActivateExperimentalFeature(ASExperimentalNewDefaultCellLayoutMode)) { + _cellLayoutMode = ASCellLayoutModeSyncForSmallContent; + } else { + _cellLayoutMode = ASCellLayoutModeNone; + } + return self; } @@ -1868,17 +1874,22 @@ - (BOOL)dataController:(ASDataController *)dataController shouldSynchronouslyPro if (ASCellLayoutModeIncludes(ASCellLayoutModeAlwaysAsync)) { return NO; } - // The heuristics below apply to the ASCellLayoutModeNone. - // If we have very few ASCellNodes (besides UIKit passthrough ones), match UIKit by blocking. - if (changeSet.countForAsyncLayout < 2) { - return YES; - } - CGSize contentSize = self.contentSize; - CGSize boundsSize = self.bounds.size; - if (contentSize.height <= boundsSize.height && contentSize.width <= boundsSize.width) { - return YES; + if (ASCellLayoutModeIncludes(ASCellLayoutModeSyncForSmallContent)) { + // Reload data is expensive, don't block main while doing so. + if (changeSet.includesReloadData) { + return NO; + } + // If we have very few ASCellNodes (besides UIKit passthrough ones), match UIKit by blocking. + if (changeSet.countForAsyncLayout < 2) { + return YES; + } + CGSize contentSize = self.contentSize; + CGSize boundsSize = self.bounds.size; + if (contentSize.height <= boundsSize.height && contentSize.width <= boundsSize.width) { + return YES; + } } - return NO; + return NO; // ASCellLayoutModeNone } - (BOOL)dataControllerShouldSerializeNodeCreation:(ASDataController *)dataController @@ -2465,7 +2476,9 @@ - (void)setPrefetchingEnabled:(BOOL)prefetchingEnabled - (NSArray *)accessibilityElements { - [self waitUntilAllUpdatesAreCommitted]; + if (!ASActivateExperimentalFeature(ASExperimentalSkipAccessibilityWait)) { + [self waitUntilAllUpdatesAreCommitted]; + } return [super accessibilityElements]; } diff --git a/Source/ASCollectionViewProtocols.h b/Source/ASCollectionViewProtocols.h index 16a547d7d..49132028b 100644 --- a/Source/ASCollectionViewProtocols.h +++ b/Source/ASCollectionViewProtocols.h @@ -16,6 +16,11 @@ typedef NS_OPTIONS(NSUInteger, ASCellLayoutMode) { * each flag listed below is used. */ ASCellLayoutModeNone = 0, + /** + * If ASCellLayoutModeSyncForSmallContent is enabled it will cause ASDataController to wait on the + * background queue if the amount of new content is small. + */ + ASCellLayoutModeSyncForSmallContent = 1 << 1, /** * If ASCellLayoutModeAlwaysSync is enabled it will cause the ASDataController to wait on the * background queue, and this ensures that any new / changed cells are in the hierarchy by the @@ -26,26 +31,26 @@ typedef NS_OPTIONS(NSUInteger, ASCellLayoutMode) { * default behavior is synchronous when there are 0 or 1 ASCellNodes in the data source, and * asynchronous when there are 2 or more. */ - ASCellLayoutModeAlwaysSync = 1 << 1, // Default OFF - ASCellLayoutModeAlwaysAsync = 1 << 2, // Default OFF - ASCellLayoutModeForceIfNeeded = 1 << 3, // Deprecated, default OFF. - ASCellLayoutModeAlwaysPassthroughDelegate = 1 << 4, // Deprecated, default ON. + ASCellLayoutModeAlwaysSync = 1 << 2, // Default OFF + ASCellLayoutModeAlwaysAsync = 1 << 3, // Default OFF + ASCellLayoutModeForceIfNeeded = 1 << 4, // Deprecated, default OFF. + ASCellLayoutModeAlwaysPassthroughDelegate = 1 << 5, // Deprecated, default ON. /** Instead of using performBatchUpdates: prefer using reloadData for changes for collection view */ - ASCellLayoutModeAlwaysReloadData = 1 << 5, // Default OFF + ASCellLayoutModeAlwaysReloadData = 1 << 6, // Default OFF /** If flag is enabled nodes are *not* gonna be range managed. */ - ASCellLayoutModeDisableRangeController = 1 << 6, // Default OFF - ASCellLayoutModeAlwaysLazy = 1 << 7, // Deprecated, default OFF. + ASCellLayoutModeDisableRangeController = 1 << 7, // Default OFF + ASCellLayoutModeAlwaysLazy = 1 << 8, // Deprecated, default OFF. /** * Defines if the node creation should happen serialized and not in parallel within the * data controller */ - ASCellLayoutModeSerializeNodeCreation = 1 << 8, // Default OFF + ASCellLayoutModeSerializeNodeCreation = 1 << 9, // Default OFF /** * When set, the performBatchUpdates: API (including animation) is used when handling Section * Reload operations. This is useful only when ASCellLayoutModeAlwaysReloadData is enabled and * cell height animations are desired. */ - ASCellLayoutModeAlwaysBatchUpdateSectionReload = 1 << 9 // Default OFF + ASCellLayoutModeAlwaysBatchUpdateSectionReload = 1 << 10 // Default OFF }; NS_ASSUME_NONNULL_BEGIN diff --git a/Source/ASExperimentalFeatures.h b/Source/ASExperimentalFeatures.h index 8b85d312a..8061785a8 100644 --- a/Source/ASExperimentalFeatures.h +++ b/Source/ASExperimentalFeatures.h @@ -29,6 +29,8 @@ typedef NS_OPTIONS(NSUInteger, ASExperimentalFeatures) { ASExperimentalSkipClearData = 1 << 8, // exp_skip_clear_data ASExperimentalDidEnterPreloadSkipASMLayout = 1 << 9, // exp_did_enter_preload_skip_asm_layout ASExperimentalDisableAccessibilityCache = 1 << 10, // exp_disable_a11y_cache + ASExperimentalSkipAccessibilityWait = 1 << 11, // exp_skip_a11y_wait + ASExperimentalNewDefaultCellLayoutMode = 1 << 12, // exp_new_default_cell_layout_mode ASExperimentalFeatureAll = 0xFFFFFFFF }; diff --git a/Source/ASExperimentalFeatures.mm b/Source/ASExperimentalFeatures.mm index e1e3fce55..3f005beed 100644 --- a/Source/ASExperimentalFeatures.mm +++ b/Source/ASExperimentalFeatures.mm @@ -22,7 +22,9 @@ @"exp_framesetter_cache", @"exp_skip_clear_data", @"exp_did_enter_preload_skip_asm_layout", - @"exp_disable_a11y_cache"])); + @"exp_disable_a11y_cache", + @"exp_skip_a11y_wait", + @"exp_new_default_cell_layout_mode"])); if (flags == ASExperimentalFeatureAll) { return allNames; diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 4850495bb..656b943f5 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -1677,14 +1677,20 @@ - (BOOL)dataControllerShouldSerializeNodeCreation:(ASDataController *)dataContro - (BOOL)dataController:(ASDataController *)dataController shouldSynchronouslyProcessChangeSet:(_ASHierarchyChangeSet *)changeSet { - // For more details on this method, see the comment in the ASCollectionView implementation. - if (changeSet.countForAsyncLayout < 2) { - return YES; - } - CGSize contentSize = self.contentSize; - CGSize boundsSize = self.bounds.size; - if (contentSize.height <= boundsSize.height && contentSize.width <= boundsSize.width) { - return YES; + if (ASActivateExperimentalFeature(ASExperimentalNewDefaultCellLayoutMode)) { + // Reload data is expensive, don't block main while doing so. + if (changeSet.includesReloadData) { + return NO; + } + // For more details on this method, see the comment in the ASCollectionView implementation. + if (changeSet.countForAsyncLayout < 2) { + return YES; + } + CGSize contentSize = self.contentSize; + CGSize boundsSize = self.bounds.size; + if (contentSize.height <= boundsSize.height && contentSize.width <= boundsSize.width) { + return YES; + } } return NO; } @@ -2004,7 +2010,9 @@ - (void)didMoveToSuperview - (NSArray *)accessibilityElements { - [self waitUntilAllUpdatesAreCommitted]; + if (!ASActivateExperimentalFeature(ASExperimentalSkipAccessibilityWait)) { + [self waitUntilAllUpdatesAreCommitted]; + } return [super accessibilityElements]; } diff --git a/Tests/ASCollectionViewTests.mm b/Tests/ASCollectionViewTests.mm index c4fc4c914..b1476f3aa 100644 --- a/Tests/ASCollectionViewTests.mm +++ b/Tests/ASCollectionViewTests.mm @@ -163,7 +163,6 @@ - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibB @interface ASCollectionView (InternalTesting) - (NSArray *)dataController:(ASDataController *)dataController supplementaryNodeKindsInSections:(NSIndexSet *)sections; -- (BOOL)dataController:(ASDataController *)dataController shouldSynchronouslyProcessChangeSet:(_ASHierarchyChangeSet *)changeSet; @end @@ -1047,15 +1046,24 @@ - (void)_primitiveBatchFetchingFillTestAnimated:(BOOL)animated visible:(BOOL)vis - (void)testInitialRangeBounds { - [self testInitialRangeBoundsWithCellLayoutMode:ASCellLayoutModeNone]; + [self testInitialRangeBoundsWithCellLayoutMode:ASCellLayoutModeNone + shouldWaitUntilAllUpdatesAreProcessed:YES]; +} + +- (void)testInitialRangeBoundsCellLayoutModeSyncForSmallContent +{ + [self testInitialRangeBoundsWithCellLayoutMode:ASCellLayoutModeSyncForSmallContent + shouldWaitUntilAllUpdatesAreProcessed:YES]; // Need to wait because the first initial data load is always async } - (void)testInitialRangeBoundsCellLayoutModeAlwaysAsync { - [self testInitialRangeBoundsWithCellLayoutMode:ASCellLayoutModeAlwaysAsync]; + [self testInitialRangeBoundsWithCellLayoutMode:ASCellLayoutModeAlwaysAsync + shouldWaitUntilAllUpdatesAreProcessed:YES]; } - (void)testInitialRangeBoundsWithCellLayoutMode:(ASCellLayoutMode)cellLayoutMode + shouldWaitUntilAllUpdatesAreProcessed:(BOOL)shouldWait { UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil]; @@ -1071,9 +1079,7 @@ - (void)testInitialRangeBoundsWithCellLayoutMode:(ASCellLayoutMode)cellLayoutMod // Trigger the initial reload to start [window layoutIfNeeded]; - // Test the APIs that monitor ASCollectionNode update handling if collection node should - // layout asynchronously - if (![cn.view dataController:nil shouldSynchronouslyProcessChangeSet:nil]) { + if (shouldWait) { XCTAssertTrue(cn.isProcessingUpdates, @"ASCollectionNode should still be processing updates after initial layoutIfNeeded call (reloadData)"); [cn onDidFinishProcessingUpdates:^{ diff --git a/Tests/ASConfigurationTests.mm b/Tests/ASConfigurationTests.mm index 7ea48ddaf..b5aeeb656 100644 --- a/Tests/ASConfigurationTests.mm +++ b/Tests/ASConfigurationTests.mm @@ -28,7 +28,9 @@ ASExperimentalFramesetterCache, ASExperimentalSkipClearData, ASExperimentalDidEnterPreloadSkipASMLayout, - ASExperimentalDisableAccessibilityCache + ASExperimentalDisableAccessibilityCache, + ASExperimentalSkipAccessibilityWait, + ASExperimentalNewDefaultCellLayoutMode }; @interface ASConfigurationTests : ASTestCase @@ -51,7 +53,9 @@ + (NSArray *)names { @"exp_framesetter_cache", @"exp_skip_clear_data", @"exp_did_enter_preload_skip_asm_layout", - @"exp_disable_a11y_cache" + @"exp_disable_a11y_cache", + @"exp_skip_a11y_wait", + @"exp_new_default_cell_layout_mode" ]; }