From bba6b252a42f5f010f7133854898b02127028ccd Mon Sep 17 00:00:00 2001 From: Maxime Ollivier Date: Tue, 8 Sep 2020 09:06:16 -0700 Subject: [PATCH] create IGListUpdateTransactionBuilder Summary: Now, lets move pending changes to a separate object `IGListUpdateTransactionBuilder`, which can we discarded once the update actually starts. Reviewed By: patters Differential Revision: D23145774 fbshipit-source-id: e12de178de33497f476972a9ad89ebb4e8d413ab --- .../IGListExperimentalAdapterUpdater.m | 85 +++++------------ ...IGListExperimentalAdapterUpdaterInternal.h | 13 +-- .../Internal/IGListUpdateTransactionBuilder.h | 79 +++++++++++++++ .../Internal/IGListUpdateTransactionBuilder.m | 95 +++++++++++++++++++ 4 files changed, 200 insertions(+), 72 deletions(-) create mode 100644 Source/IGListKit/Internal/IGListUpdateTransactionBuilder.h create mode 100644 Source/IGListKit/Internal/IGListUpdateTransactionBuilder.m diff --git a/Source/IGListKit/IGListExperimentalAdapterUpdater.m b/Source/IGListKit/IGListExperimentalAdapterUpdater.m index 27c29cc3..7682ad6a 100644 --- a/Source/IGListKit/IGListExperimentalAdapterUpdater.m +++ b/Source/IGListKit/IGListExperimentalAdapterUpdater.m @@ -35,10 +35,7 @@ typedef void (^IGListAdapterUpdaterCompletionBlock)(BOOL); IGAssertMainThread(); if (self = [super init]) { - // the default is to use animations unless NO is passed - _queuedUpdateIsAnimated = YES; - _completionBlocks = [NSMutableArray new]; - _itemUpdateBlocks = [NSMutableArray new]; + _transactionBuilder = [IGListUpdateTransactionBuilder new]; _allowsBackgroundReloading = YES; _allowsReloadingOnTooManyUpdates = YES; } @@ -48,18 +45,16 @@ typedef void (^IGListAdapterUpdaterCompletionBlock)(BOOL); #pragma mark - Private API - (BOOL)hasChanges { - return self.hasQueuedReloadData - || self.itemUpdateBlocks.count > 0 - || self.dataBlock != nil; + return [self.transactionBuilder hasChanges]; } - (void)performReloadDataWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock { IGAssertMainThread(); id delegate = self.delegate; - void (^reloadUpdates)(void) = self.reloadUpdates; - NSArray *itemUpdateBlocks = [self.itemUpdateBlocks copy]; - NSArray *completionBlocks = [self.completionBlocks copy]; + void (^reloadUpdates)(void) = [self.transactionBuilder reloadBlock]; + NSArray *itemUpdateBlocks = [self.transactionBuilder itemUpdateBlocks]; + NSArray *completionBlocks = [self.transactionBuilder completionBlocks]; [self cleanStateBeforeUpdates]; @@ -118,13 +113,13 @@ typedef void (^IGListAdapterUpdaterCompletionBlock)(BOOL); // create local variables so we can immediately clean our state but pass these items into the batch update block id delegate = self.delegate; - IGListTransitionDataBlock dataBlock = [self.dataBlock copy]; - IGListTransitionDataApplyBlock applyDataBlock = [self.applyDataBlock copy]; - NSArray *completionBlocks = [self.completionBlocks copy]; - const BOOL animated = self.queuedUpdateIsAnimated; + IGListTransitionDataBlock dataBlock = [self.transactionBuilder dataBlock]; + IGListTransitionDataApplyBlock applyDataBlock = [self.transactionBuilder applyDataBlock]; + NSArray *completionBlocks = [self.transactionBuilder completionBlocks]; + const BOOL animated = [self.transactionBuilder animated]; const BOOL allowsReloadingOnTooManyUpdates = self.allowsReloadingOnTooManyUpdates; const IGListExperiment experiments = self.experiments; - NSArray *itemUpdateBlocks = [self.itemUpdateBlocks copy]; + NSArray *itemUpdateBlocks = [self.transactionBuilder itemUpdateBlocks]; // clean up all state so that new updates can be coalesced while the current update is in flight [self cleanStateBeforeUpdates]; @@ -319,23 +314,8 @@ willPerformBatchUpdatesWithCollectionView:collectionView } - (void)cleanStateBeforeUpdates { - self.queuedUpdateIsAnimated = YES; + _transactionBuilder = [IGListUpdateTransactionBuilder new]; - // destroy to/from transition items - self.dataBlock = nil; - - // destroy reloadData state - self.reloadUpdates = nil; - self.queuedReloadData = NO; - - // remove indexpath/item changes - self.applyDataBlock = nil; - - // removes all object completion blocks. done before updates to start collecting completion blocks for coalesced - // or re-entrant object updates - [self.completionBlocks removeAllObjects]; - - [self.itemUpdateBlocks removeAllObjects]; self.inUpdateCompletionBlocks = [NSMutableArray new]; self.inUpdateItemCollector = [IGListItemUpdatesCollector new]; } @@ -360,7 +340,7 @@ willPerformBatchUpdatesWithCollectionView:collectionView return; } - if (weakSelf.hasQueuedReloadData) { + if (weakSelf.transactionBuilder.hasReloadData) { [weakSelf performReloadDataWithCollectionViewBlock:collectionViewBlock]; } else { [weakSelf performBatchUpdatesWithCollectionViewBlock:collectionViewBlock]; @@ -410,20 +390,11 @@ static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(cons IGParameterAssert(dataBlock != nil); IGParameterAssert(applyDataBlock != nil); - // will call the dataBlock after the dispatch - self.dataBlock = dataBlock; - - // always use the last update block, even though this should always do the exact same thing - self.applyDataBlock = applyDataBlock; - - // disabled animations will always take priority - // reset to YES in -cleanupState - self.queuedUpdateIsAnimated = self.queuedUpdateIsAnimated && animated; - - IGListUpdatingCompletion localCompletion = completion; - if (localCompletion) { - [self.completionBlocks addObject:localCompletion]; - } + [self.transactionBuilder addSectionBatchUpdateAnimated:animated + collectionViewBlock:collectionViewBlock + dataBlock:dataBlock + applyDataBlock:applyDataBlock + completion:completion]; [self _queueUpdateWithCollectionViewBlock:collectionViewBlock]; } @@ -445,15 +416,10 @@ static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(cons } itemUpdates(); } else { - if (completion != nil) { - [self.completionBlocks addObject:completion]; - } - - [self.itemUpdateBlocks addObject:itemUpdates]; - - // disabled animations will always take priority - // reset to YES in -cleanupState - self.queuedUpdateIsAnimated = self.queuedUpdateIsAnimated && animated; + [self.transactionBuilder addItemBatchUpdateAnimated:animated + collectionViewBlock:collectionViewBlock + itemUpdates:itemUpdates + completion:completion]; [self _queueUpdateWithCollectionViewBlock:collectionViewBlock]; } @@ -554,13 +520,10 @@ static NSUInteger IGListIdentifierHash(const void *item, NSUInteger (*size)(cons IGParameterAssert(collectionViewBlock != nil); IGParameterAssert(reloadUpdateBlock != nil); - IGListUpdatingCompletion localCompletion = completion; - if (localCompletion) { - [self.completionBlocks addObject:localCompletion]; - } + [self.transactionBuilder addReloadDataWithCollectionViewBlock:collectionViewBlock + reloadBlock:reloadUpdateBlock + completion:completion]; - self.reloadUpdates = reloadUpdateBlock; - self.queuedReloadData = YES; [self _queueUpdateWithCollectionViewBlock:collectionViewBlock]; } diff --git a/Source/IGListKit/Internal/IGListExperimentalAdapterUpdaterInternal.h b/Source/IGListKit/Internal/IGListExperimentalAdapterUpdaterInternal.h index 76e63d71..962d9427 100644 --- a/Source/IGListKit/Internal/IGListExperimentalAdapterUpdaterInternal.h +++ b/Source/IGListKit/Internal/IGListExperimentalAdapterUpdaterInternal.h @@ -14,26 +14,17 @@ #import "IGListExperimentalAdapterUpdater.h" #import "IGListBatchUpdateState.h" #import "IGListItemUpdatesCollector.h" +#import "IGListUpdateTransactionBuilder.h" NS_ASSUME_NONNULL_BEGIN @interface IGListExperimentalAdapterUpdater () -@property (nonatomic, copy, nullable) IGListTransitionDataBlock dataBlock; -@property (nonatomic, strong) NSMutableArray *completionBlocks; - -@property (nonatomic, assign) BOOL queuedUpdateIsAnimated; - -@property (nonatomic, strong) NSMutableArray *itemUpdateBlocks; +@property (nonatomic, strong, readonly) IGListUpdateTransactionBuilder *transactionBuilder; @property (nonatomic, strong) NSMutableArray *inUpdateCompletionBlocks; @property (nonatomic, strong) IGListItemUpdatesCollector *inUpdateItemCollector; -@property (nonatomic, copy, nullable) IGListTransitionDataApplyBlock applyDataBlock; - -@property (nonatomic, copy, nullable) IGListReloadUpdateBlock reloadUpdates; -@property (nonatomic, assign, getter=hasQueuedReloadData) BOOL queuedReloadData; - @property (nonatomic, assign) IGListBatchUpdateState state; @property (nonatomic, strong, nullable) IGListBatchUpdateData *applyingUpdateData; diff --git a/Source/IGListKit/Internal/IGListUpdateTransactionBuilder.h b/Source/IGListKit/Internal/IGListUpdateTransactionBuilder.h new file mode 100644 index 00000000..95f992db --- /dev/null +++ b/Source/IGListKit/Internal/IGListUpdateTransactionBuilder.h @@ -0,0 +1,79 @@ +/* +* Copyright (c) Facebook, Inc. and its affiliates. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +#import + +#import +#import +#import + +@protocol IGListAdapterUpdaterDelegate; +@protocol IGListAdapterUpdating; + +NS_ASSUME_NONNULL_BEGIN + +/// Class to collect reload & update information before actually starting the transition. +IGLK_SUBCLASSING_RESTRICTED +@interface IGListUpdateTransactionBuilder : NSObject + +/** + Add a section-level update. + + @param animated A flag indicating if the transition should be animated. + @param collectionViewBlock A block returning the collecion view to perform updates on. + @param dataBlock A block which returns the transition data + @param applyDataBlock A block that applies the data passed from the `dataBlock` block + @param completion A completion block to execute when the update is finished. +*/ +- (void)addSectionBatchUpdateAnimated:(BOOL)animated + collectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock + dataBlock:(IGListTransitionDataBlock)dataBlock + applyDataBlock:(IGListTransitionDataApplyBlock)applyDataBlock + completion:(nullable IGListUpdatingCompletion)completion; + +/** + Add a item-level update. + + @param animated A flag indicating if the transition should be animated. + @param collectionViewBlock A block returning the collecion view to perform updates on. + @param itemUpdates A block containing all of the updates. + @param completion A completion block to execute when the update is finished. +*/ +- (void)addItemBatchUpdateAnimated:(BOOL)animated + collectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock + itemUpdates:(IGListItemUpdateBlock)itemUpdates + completion:(nullable IGListUpdatingCompletion)completion; + +/** +Completely reload data in the collection. + +@param collectionViewBlock A block returning the collecion view to reload. +@param reloadBlock A block that must be called when the adapter reloads the collection view. +@param completion A completion block to execute when the reload is finished. +*/ +- (void)addReloadDataWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock + reloadBlock:(IGListReloadUpdateBlock)reloadBlock + completion:(nullable IGListUpdatingCompletion)completion; + +// Batch updates +- (nullable IGListTransitionDataBlock)dataBlock; +- (nullable IGListTransitionDataApplyBlock)applyDataBlock; +- (NSMutableArray *)itemUpdateBlocks; +- (BOOL)animated; + +// Reload +- (BOOL)hasReloadData; +- (nullable IGListReloadUpdateBlock)reloadBlock; + +// Both +- (nullable IGListCollectionViewBlock)collectionViewBlock; +- (NSMutableArray *)completionBlocks; +- (BOOL)hasChanges; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/IGListKit/Internal/IGListUpdateTransactionBuilder.m b/Source/IGListKit/Internal/IGListUpdateTransactionBuilder.m new file mode 100644 index 00000000..fdab2026 --- /dev/null +++ b/Source/IGListKit/Internal/IGListUpdateTransactionBuilder.m @@ -0,0 +1,95 @@ +/* +* Copyright (c) Facebook, Inc. and its affiliates. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +#import "IGListUpdateTransactionBuilder.h" + +@interface IGListUpdateTransactionBuilder () +// Batch updates +@property (nonatomic, copy, readwrite, nullable) IGListTransitionDataBlock dataBlock; +@property (nonatomic, copy, readwrite, nullable) IGListTransitionDataApplyBlock applyDataBlock; +@property (nonatomic, strong, readonly) NSMutableArray *itemUpdateBlocks; +@property (nonatomic, assign, readwrite) BOOL animated; +// Reload +@property (nonatomic, assign, readwrite) BOOL hasReloadData; +@property (nonatomic, copy, readwrite, nullable) IGListReloadUpdateBlock reloadBlock; +// Both +@property (nonatomic, copy, readwrite, nullable) IGListCollectionViewBlock collectionViewBlock; +@property (nonatomic, strong, readonly) NSMutableArray *completionBlocks; +@end + +@implementation IGListUpdateTransactionBuilder + +- (instancetype)init { + if (self = [super init]) { + _animated = YES; + _itemUpdateBlocks = [NSMutableArray new]; + _completionBlocks = [NSMutableArray new]; + } + return self; +} + +#pragma mark - Add changes + +- (void)addSectionBatchUpdateAnimated:(BOOL)animated + collectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock + dataBlock:(IGListTransitionDataBlock)dataBlock + applyDataBlock:(IGListTransitionDataApplyBlock)applyDataBlock + completion:(IGListUpdatingCompletion)completion { + // disabled animations will always take priority + // reset to YES in -cleanupState + self.animated = self.animated && animated; + self.collectionViewBlock = collectionViewBlock; + + // will call the dataBlock after the dispatch + self.dataBlock = dataBlock; + + // always use the last update block, even though this should always do the exact same thing + self.applyDataBlock = applyDataBlock; + + IGListUpdatingCompletion localCompletion = completion; + if (localCompletion) { + [self.completionBlocks addObject:localCompletion]; + } +} + +- (void)addItemBatchUpdateAnimated:(BOOL)animated + collectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock + itemUpdates:(IGListItemUpdateBlock)itemUpdates + completion:(nullable IGListUpdatingCompletion)completion { + // disabled animations will always take priority + // reset to YES in -cleanupState + self.animated = self.animated && animated; + self.collectionViewBlock = collectionViewBlock; + + [self.itemUpdateBlocks addObject:itemUpdates]; + + IGListUpdatingCompletion localCompletion = completion; + if (localCompletion) { + [self.completionBlocks addObject:localCompletion]; + } +} + +- (void)addReloadDataWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock + reloadBlock:(IGListReloadUpdateBlock)reloadBlock + completion:(nullable IGListUpdatingCompletion)completion { + self.hasReloadData = YES; + self.collectionViewBlock = collectionViewBlock; + self.reloadBlock = reloadBlock; + + IGListUpdatingCompletion localCompletion = completion; + if (localCompletion) { + [self.completionBlocks addObject:localCompletion]; + } +} + +- (BOOL)hasChanges { + return self.hasReloadData + || self.itemUpdateBlocks.count > 0 + || self.dataBlock != nil; +} + +@end