mirror of
https://github.com/Instagram/IGListKit
synced 2026-05-23 09:18:29 +00:00
Refactor and move the applyUpdate code into Helper file
Summary: I want to reuse this collection view update code in next diff, let's move it to a Helper file to support code sharing. Reviewed By: calimarkus, iperry90 Differential Revision: D18714061 fbshipit-source-id: a85e8576226276fcab90ea1d2ed23aac5501aa17
This commit is contained in:
parent
da4a5bbf25
commit
64f053c258
5 changed files with 178 additions and 122 deletions
|
|
@ -15,6 +15,7 @@
|
|||
#import "IGListMoveIndexPathInternal.h"
|
||||
#import "IGListReloadIndexPath.h"
|
||||
#import "UICollectionView+IGListBatchUpdateData.h"
|
||||
#import "IGListAdapterUpdaterHelpers.h"
|
||||
|
||||
@implementation IGListAdapterUpdater
|
||||
|
||||
|
|
@ -197,11 +198,14 @@
|
|||
// block executed in the first param block of -[UICollectionView performBatchUpdates:completion:]
|
||||
void (^batchUpdatesBlock)(IGListIndexSetResult *result) = ^(IGListIndexSetResult *result){
|
||||
executeUpdateBlocks();
|
||||
|
||||
self.applyingUpdateData = [self _flushCollectionView:collectionView
|
||||
withDiffResult:result
|
||||
batchUpdates:self.batchUpdates
|
||||
fromObjects:fromObjects];
|
||||
|
||||
self.applyingUpdateData = IGListApplyUpdatesToCollectionView(collectionView,
|
||||
result,
|
||||
self.batchUpdates,
|
||||
fromObjects,
|
||||
experiments,
|
||||
self.movesAsDeletesInserts,
|
||||
self.preferItemReloadsForSectionReloads);
|
||||
|
||||
[self _cleanStateAfterUpdates];
|
||||
[self _performBatchUpdatesItemBlockApplied];
|
||||
|
|
@ -273,114 +277,6 @@ willPerformBatchUpdatesWithCollectionView:collectionView
|
|||
}
|
||||
}
|
||||
|
||||
void convertReloadToDeleteInsert(NSMutableIndexSet *reloads,
|
||||
NSMutableIndexSet *deletes,
|
||||
NSMutableIndexSet *inserts,
|
||||
IGListIndexSetResult *result,
|
||||
NSArray<id<IGListDiffable>> *fromObjects) {
|
||||
// reloadSections: is unsafe to use within performBatchUpdates:, so instead convert all reloads into deletes+inserts
|
||||
const BOOL hasObjects = [fromObjects count] > 0;
|
||||
[[reloads copy] enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
|
||||
// if a diff was not performed, there are no changes. instead use the same index that was originally queued
|
||||
id<NSObject> diffIdentifier = hasObjects ? [fromObjects[idx] diffIdentifier] : nil;
|
||||
const NSInteger from = hasObjects ? [result oldIndexForIdentifier:diffIdentifier] : idx;
|
||||
const NSInteger to = hasObjects ? [result newIndexForIdentifier:diffIdentifier] : idx;
|
||||
[reloads removeIndex:from];
|
||||
|
||||
// if a reload is queued outside the diff and the object was inserted or deleted it cannot be
|
||||
if (from != NSNotFound && to != NSNotFound) {
|
||||
[deletes addIndex:from];
|
||||
[inserts addIndex:to];
|
||||
} else {
|
||||
IGAssert([result.deletes containsIndex:idx],
|
||||
@"Reloaded section %lu was not found in deletes with from: %li, to: %li, deletes: %@, fromClass: %@",
|
||||
(unsigned long)idx, (long)from, (long)to, deletes, [(id)fromObjects[idx] class]);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
static NSArray<NSIndexPath *> *convertSectionReloadToItemUpdates(NSIndexSet *sectionReloads, UICollectionView *collectionView) {
|
||||
NSMutableArray<NSIndexPath *> *updates = [NSMutableArray new];
|
||||
[sectionReloads enumerateIndexesUsingBlock:^(NSUInteger sectionIndex, BOOL * _Nonnull stop) {
|
||||
NSUInteger numberOfItems = [collectionView numberOfItemsInSection:sectionIndex];
|
||||
for (NSUInteger itemIndex = 0; itemIndex < numberOfItems; itemIndex++) {
|
||||
[updates addObject:[NSIndexPath indexPathForItem:itemIndex inSection:sectionIndex]];
|
||||
}
|
||||
}];
|
||||
return [updates copy];
|
||||
}
|
||||
|
||||
- (IGListBatchUpdateData *)_flushCollectionView:(UICollectionView *)collectionView
|
||||
withDiffResult:(IGListIndexSetResult *)diffResult
|
||||
batchUpdates:(IGListBatchUpdates *)batchUpdates
|
||||
fromObjects:(NSArray <id<IGListDiffable>> *)fromObjects {
|
||||
NSSet *moves = [[NSSet alloc] initWithArray:diffResult.moves];
|
||||
|
||||
// combine section reloads from the diff and manual reloads via reloadItems:
|
||||
NSMutableIndexSet *reloads = [diffResult.updates mutableCopy];
|
||||
[reloads addIndexes:batchUpdates.sectionReloads];
|
||||
|
||||
NSMutableIndexSet *inserts = [diffResult.inserts mutableCopy];
|
||||
NSMutableIndexSet *deletes = [diffResult.deletes mutableCopy];
|
||||
NSMutableArray<NSIndexPath *> *itemUpdates = [NSMutableArray new];
|
||||
if (self.movesAsDeletesInserts) {
|
||||
for (IGListMoveIndex *move in moves) {
|
||||
[deletes addIndex:move.from];
|
||||
[inserts addIndex:move.to];
|
||||
}
|
||||
// clear out all moves
|
||||
moves = [NSSet new];
|
||||
}
|
||||
|
||||
// Item reloads are not safe, if any section moves happened or there are inserts/deletes.
|
||||
if (self.preferItemReloadsForSectionReloads
|
||||
&& moves.count == 0 && inserts.count == 0 && deletes.count == 0 && reloads.count > 0) {
|
||||
[reloads enumerateIndexesUsingBlock:^(NSUInteger sectionIndex, BOOL * _Nonnull stop) {
|
||||
NSMutableIndexSet *localIndexSet = [NSMutableIndexSet indexSetWithIndex:sectionIndex];
|
||||
if (sectionIndex < [collectionView numberOfSections]
|
||||
&& sectionIndex < [collectionView.dataSource numberOfSectionsInCollectionView:collectionView]
|
||||
&& [collectionView numberOfItemsInSection:sectionIndex] == [collectionView.dataSource collectionView:collectionView numberOfItemsInSection:sectionIndex]) {
|
||||
// Perfer to do item reloads instead, if the number of items in section is unchanged.
|
||||
[itemUpdates addObjectsFromArray:convertSectionReloadToItemUpdates(localIndexSet, collectionView)];
|
||||
} else {
|
||||
// Otherwise, fallback to convert into delete+insert section operation.
|
||||
convertReloadToDeleteInsert(localIndexSet, deletes, inserts, diffResult, fromObjects);
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
// reloadSections: is unsafe to use within performBatchUpdates:, so instead convert all reloads into deletes+inserts
|
||||
convertReloadToDeleteInsert(reloads, deletes, inserts, diffResult, fromObjects);
|
||||
}
|
||||
|
||||
NSMutableArray<NSIndexPath *> *itemInserts = batchUpdates.itemInserts;
|
||||
NSMutableArray<NSIndexPath *> *itemDeletes = batchUpdates.itemDeletes;
|
||||
NSMutableArray<IGListMoveIndexPath *> *itemMoves = batchUpdates.itemMoves;
|
||||
|
||||
NSSet<NSIndexPath *> *uniqueDeletes = [NSSet setWithArray:itemDeletes];
|
||||
NSMutableSet<NSIndexPath *> *reloadDeletePaths = [NSMutableSet new];
|
||||
NSMutableSet<NSIndexPath *> *reloadInsertPaths = [NSMutableSet new];
|
||||
for (IGListReloadIndexPath *reload in batchUpdates.itemReloads) {
|
||||
if (![uniqueDeletes containsObject:reload.fromIndexPath]) {
|
||||
[reloadDeletePaths addObject:reload.fromIndexPath];
|
||||
[reloadInsertPaths addObject:reload.toIndexPath];
|
||||
}
|
||||
}
|
||||
[itemDeletes addObjectsFromArray:[reloadDeletePaths allObjects]];
|
||||
[itemInserts addObjectsFromArray:[reloadInsertPaths allObjects]];
|
||||
|
||||
const BOOL fixIndexPathImbalance = IGListExperimentEnabled(self.experiments, IGListExperimentFixIndexPathImbalance);
|
||||
IGListBatchUpdateData *updateData = [[IGListBatchUpdateData alloc] initWithInsertSections:inserts
|
||||
deleteSections:deletes
|
||||
moveSections:moves
|
||||
insertIndexPaths:itemInserts
|
||||
deleteIndexPaths:itemDeletes
|
||||
updateIndexPaths:itemUpdates
|
||||
moveIndexPaths:itemMoves
|
||||
fixIndexPathImbalance:fixIndexPathImbalance];
|
||||
[collectionView ig_applyBatchUpdateData:updateData];
|
||||
return updateData;
|
||||
}
|
||||
|
||||
- (void)_beginPerformBatchUpdatesToObjects:(NSArray *)toObjects {
|
||||
self.pendingTransitionToObjects = toObjects;
|
||||
self.state = IGListBatchUpdateStateQueuedBatchUpdate;
|
||||
|
|
|
|||
33
Source/IGListKit/Internal/IGListAdapterUpdaterHelpers.h
Normal file
33
Source/IGListKit/Internal/IGListAdapterUpdaterHelpers.h
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* 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 <UIKit/UIKit.h>
|
||||
|
||||
#import <IGListDiffKit/IGListExperiments.h>
|
||||
|
||||
@class IGListBatchUpdateData;
|
||||
@class IGListIndexSetResult;
|
||||
@class IGListBatchUpdates;
|
||||
@protocol IGListDiffable;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
extern void IGListConvertReloadToDeleteInsert(NSMutableIndexSet *reloads,
|
||||
NSMutableIndexSet *deletes,
|
||||
NSMutableIndexSet *inserts,
|
||||
IGListIndexSetResult *result,
|
||||
NSArray<id<IGListDiffable>> *fromObjects);
|
||||
|
||||
extern IGListBatchUpdateData *IGListApplyUpdatesToCollectionView(UICollectionView *collectionView,
|
||||
IGListIndexSetResult *diffResult,
|
||||
IGListBatchUpdates *batchUpdates,
|
||||
NSArray<id<IGListDiffable>> *fromObjects,
|
||||
IGListExperiment experiments,
|
||||
BOOL movesAsDeletesInserts,
|
||||
BOOL preferItemReloadsForSectionReloads);
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
132
Source/IGListKit/Internal/IGListAdapterUpdaterHelpers.m
Normal file
132
Source/IGListKit/Internal/IGListAdapterUpdaterHelpers.m
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
/**
|
||||
* 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 "IGListAdapterUpdaterHelpers.h"
|
||||
|
||||
#import <IGAssert/IGAssert.h>
|
||||
|
||||
#import <IGListDiffKit/IGListBatchUpdateData.h>
|
||||
#import <IGListDiffKit/IGListIndexSetResult.h>
|
||||
#import <IGListDiffKit/IGListDiffable.h>
|
||||
#import <IGListDiffKit/IGListExperiments.h>
|
||||
|
||||
#import "IGListBatchUpdates.h"
|
||||
#import "IGListReloadIndexPath.h"
|
||||
#import "UICollectionView+IGListBatchUpdateData.h"
|
||||
|
||||
void IGListConvertReloadToDeleteInsert(NSMutableIndexSet *reloads,
|
||||
NSMutableIndexSet *deletes,
|
||||
NSMutableIndexSet *inserts,
|
||||
IGListIndexSetResult *result,
|
||||
NSArray<id<IGListDiffable>> *fromObjects) {
|
||||
// reloadSections: is unsafe to use within performBatchUpdates:, so instead convert all reloads into deletes+inserts
|
||||
const BOOL hasObjects = [fromObjects count] > 0;
|
||||
[[reloads copy] enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
|
||||
// if a diff was not performed, there are no changes. instead use the same index that was originally queued
|
||||
id<NSObject> diffIdentifier = hasObjects ? [fromObjects[idx] diffIdentifier] : nil;
|
||||
const NSInteger from = hasObjects ? [result oldIndexForIdentifier:diffIdentifier] : idx;
|
||||
const NSInteger to = hasObjects ? [result newIndexForIdentifier:diffIdentifier] : idx;
|
||||
[reloads removeIndex:from];
|
||||
|
||||
// if a reload is queued outside the diff and the object was inserted or deleted it cannot be
|
||||
if (from != NSNotFound && to != NSNotFound) {
|
||||
[deletes addIndex:from];
|
||||
[inserts addIndex:to];
|
||||
} else {
|
||||
IGAssert([result.deletes containsIndex:idx],
|
||||
@"Reloaded section %lu was not found in deletes with from: %li, to: %li, deletes: %@, fromClass: %@",
|
||||
(unsigned long)idx, (long)from, (long)to, deletes, [(id)fromObjects[idx] class]);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
static NSArray<NSIndexPath *> *convertSectionReloadToItemUpdates(NSIndexSet *sectionReloads, UICollectionView *collectionView) {
|
||||
NSMutableArray<NSIndexPath *> *updates = [NSMutableArray new];
|
||||
[sectionReloads enumerateIndexesUsingBlock:^(NSUInteger sectionIndex, BOOL * _Nonnull stop) {
|
||||
NSUInteger numberOfItems = [collectionView numberOfItemsInSection:sectionIndex];
|
||||
for (NSUInteger itemIndex = 0; itemIndex < numberOfItems; itemIndex++) {
|
||||
[updates addObject:[NSIndexPath indexPathForItem:itemIndex inSection:sectionIndex]];
|
||||
}
|
||||
}];
|
||||
return [updates copy];
|
||||
}
|
||||
|
||||
IGListBatchUpdateData *IGListApplyUpdatesToCollectionView(UICollectionView *collectionView,
|
||||
IGListIndexSetResult *diffResult,
|
||||
IGListBatchUpdates *batchUpdates,
|
||||
NSArray<id<IGListDiffable>> *fromObjects,
|
||||
IGListExperiment experiments,
|
||||
BOOL movesAsDeletesInserts,
|
||||
BOOL preferItemReloadsForSectionReloads) {
|
||||
NSSet *moves = [[NSSet alloc] initWithArray:diffResult.moves];
|
||||
|
||||
// combine section reloads from the diff and manual reloads via reloadItems:
|
||||
NSMutableIndexSet *reloads = [diffResult.updates mutableCopy];
|
||||
[reloads addIndexes:batchUpdates.sectionReloads];
|
||||
|
||||
NSMutableIndexSet *inserts = [diffResult.inserts mutableCopy];
|
||||
NSMutableIndexSet *deletes = [diffResult.deletes mutableCopy];
|
||||
NSMutableArray<NSIndexPath *> *itemUpdates = [NSMutableArray new];
|
||||
if (movesAsDeletesInserts) {
|
||||
for (IGListMoveIndex *move in moves) {
|
||||
[deletes addIndex:move.from];
|
||||
[inserts addIndex:move.to];
|
||||
}
|
||||
// clear out all moves
|
||||
moves = [NSSet new];
|
||||
}
|
||||
|
||||
// Item reloads are not safe, if any section moves happened or there are inserts/deletes.
|
||||
if (preferItemReloadsForSectionReloads
|
||||
&& moves.count == 0 && inserts.count == 0 && deletes.count == 0 && reloads.count > 0) {
|
||||
[reloads enumerateIndexesUsingBlock:^(NSUInteger sectionIndex, BOOL * _Nonnull stop) {
|
||||
NSMutableIndexSet *localIndexSet = [NSMutableIndexSet indexSetWithIndex:sectionIndex];
|
||||
if (sectionIndex < [collectionView numberOfSections]
|
||||
&& sectionIndex < [collectionView.dataSource numberOfSectionsInCollectionView:collectionView]
|
||||
&& [collectionView numberOfItemsInSection:sectionIndex] == [collectionView.dataSource collectionView:collectionView numberOfItemsInSection:sectionIndex]) {
|
||||
// Perfer to do item reloads instead, if the number of items in section is unchanged.
|
||||
[itemUpdates addObjectsFromArray:convertSectionReloadToItemUpdates(localIndexSet, collectionView)];
|
||||
} else {
|
||||
// Otherwise, fallback to convert into delete+insert section operation.
|
||||
IGListConvertReloadToDeleteInsert(localIndexSet, deletes, inserts, diffResult, fromObjects);
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
// reloadSections: is unsafe to use within performBatchUpdates:, so instead convert all reloads into deletes+inserts
|
||||
IGListConvertReloadToDeleteInsert(reloads, deletes, inserts, diffResult, fromObjects);
|
||||
}
|
||||
|
||||
NSMutableArray<NSIndexPath *> *itemInserts = batchUpdates.itemInserts;
|
||||
NSMutableArray<NSIndexPath *> *itemDeletes = batchUpdates.itemDeletes;
|
||||
NSMutableArray<IGListMoveIndexPath *> *itemMoves = batchUpdates.itemMoves;
|
||||
|
||||
NSSet<NSIndexPath *> *uniqueDeletes = [NSSet setWithArray:itemDeletes];
|
||||
NSMutableSet<NSIndexPath *> *reloadDeletePaths = [NSMutableSet new];
|
||||
NSMutableSet<NSIndexPath *> *reloadInsertPaths = [NSMutableSet new];
|
||||
for (IGListReloadIndexPath *reload in batchUpdates.itemReloads) {
|
||||
if (![uniqueDeletes containsObject:reload.fromIndexPath]) {
|
||||
[reloadDeletePaths addObject:reload.fromIndexPath];
|
||||
[reloadInsertPaths addObject:reload.toIndexPath];
|
||||
}
|
||||
}
|
||||
[itemDeletes addObjectsFromArray:[reloadDeletePaths allObjects]];
|
||||
[itemInserts addObjectsFromArray:[reloadInsertPaths allObjects]];
|
||||
|
||||
const BOOL fixIndexPathImbalance = IGListExperimentEnabled(experiments, IGListExperimentFixIndexPathImbalance);
|
||||
IGListBatchUpdateData *updateData = [[IGListBatchUpdateData alloc] initWithInsertSections:inserts
|
||||
deleteSections:deletes
|
||||
moveSections:moves
|
||||
insertIndexPaths:itemInserts
|
||||
deleteIndexPaths:itemDeletes
|
||||
updateIndexPaths:itemUpdates
|
||||
moveIndexPaths:itemMoves
|
||||
fixIndexPathImbalance:fixIndexPathImbalance];
|
||||
[collectionView ig_applyBatchUpdateData:updateData];
|
||||
return updateData;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -16,12 +16,6 @@
|
|||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
FOUNDATION_EXTERN void convertReloadToDeleteInsert(NSMutableIndexSet *reloads,
|
||||
NSMutableIndexSet *deletes,
|
||||
NSMutableIndexSet *inserts,
|
||||
IGListIndexSetResult *result,
|
||||
NSArray<id<IGListDiffable>> *fromObjects);
|
||||
|
||||
@interface IGListAdapterUpdater ()
|
||||
|
||||
@property (nonatomic, copy, nullable) NSArray *fromObjects;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#import "IGListTestUICollectionViewDataSource.h"
|
||||
#import "IGTestObject.h"
|
||||
#import "IGListMoveIndexInternal.h"
|
||||
#import "IGListAdapterUpdaterHelpers.h"
|
||||
|
||||
#define genExpectation [self expectationWithDescription:NSStringFromSelector(_cmd)]
|
||||
#define waitExpectation [self waitForExpectationsWithTimeout:30 handler:nil]
|
||||
|
|
@ -396,7 +397,7 @@
|
|||
[reloads addIndex:2];
|
||||
NSMutableIndexSet *deletes = [result.deletes mutableCopy];
|
||||
NSMutableIndexSet *inserts = [result.inserts mutableCopy];
|
||||
convertReloadToDeleteInsert(reloads, deletes, inserts, result, from);
|
||||
IGListConvertReloadToDeleteInsert(reloads, deletes, inserts, result, from);
|
||||
XCTAssertEqual(reloads.count, 0);
|
||||
XCTAssertEqual(deletes.count, 1);
|
||||
XCTAssertEqual(inserts.count, 1);
|
||||
|
|
@ -412,7 +413,7 @@
|
|||
[reloads addIndex:2];
|
||||
NSMutableIndexSet *deletes = [result.deletes mutableCopy];
|
||||
NSMutableIndexSet *inserts = [result.inserts mutableCopy];
|
||||
convertReloadToDeleteInsert(reloads, deletes, inserts, result, from);
|
||||
IGListConvertReloadToDeleteInsert(reloads, deletes, inserts, result, from);
|
||||
XCTAssertEqual(reloads.count, 0);
|
||||
XCTAssertEqual(deletes.count, 1);
|
||||
XCTAssertEqual(inserts.count, 1);
|
||||
|
|
@ -427,7 +428,7 @@
|
|||
NSMutableIndexSet *reloads = [NSMutableIndexSet indexSetWithIndex:1];
|
||||
NSMutableIndexSet *deletes = [NSMutableIndexSet new];
|
||||
NSMutableIndexSet *inserts = [NSMutableIndexSet new];
|
||||
convertReloadToDeleteInsert(reloads, deletes, inserts, result, from);
|
||||
IGListConvertReloadToDeleteInsert(reloads, deletes, inserts, result, from);
|
||||
XCTAssertEqual(reloads.count, 0);
|
||||
XCTAssertEqual(deletes.count, 0);
|
||||
XCTAssertEqual(inserts.count, 0);
|
||||
|
|
|
|||
Loading…
Reference in a new issue