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
This commit is contained in:
Maxime Ollivier 2020-09-08 09:06:16 -07:00 committed by Facebook GitHub Bot
parent 8c1253246f
commit bba6b252a4
4 changed files with 200 additions and 72 deletions

View file

@ -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<IGListAdapterUpdaterDelegate> 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<IGListAdapterUpdaterDelegate> 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];
}

View file

@ -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<IGListUpdatingCompletion> *completionBlocks;
@property (nonatomic, assign) BOOL queuedUpdateIsAnimated;
@property (nonatomic, strong) NSMutableArray<IGListItemUpdateBlock> *itemUpdateBlocks;
@property (nonatomic, strong, readonly) IGListUpdateTransactionBuilder *transactionBuilder;
@property (nonatomic, strong) NSMutableArray<IGListUpdatingCompletion> *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;

View file

@ -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 <Foundation/Foundation.h>
#import <IGListDiffKit/IGListMacros.h>
#import <IGListKit/IGListUpdatingDelegate.h>
#import <IGListKit/IGListUpdatingDelegateExperimental.h>
@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<IGListItemUpdateBlock> *)itemUpdateBlocks;
- (BOOL)animated;
// Reload
- (BOOL)hasReloadData;
- (nullable IGListReloadUpdateBlock)reloadBlock;
// Both
- (nullable IGListCollectionViewBlock)collectionViewBlock;
- (NSMutableArray<IGListUpdatingCompletion> *)completionBlocks;
- (BOOL)hasChanges;
@end
NS_ASSUME_NONNULL_END

View file

@ -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<IGListItemUpdateBlock> *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<IGListUpdatingCompletion> *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