blob: fbd6c20ae49b6efa46bbbf7836b27356dd4d3cdd [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#import "WXRecycleListUpdateManager.h"
#import "WXLog.h"
#import "WXAssert.h"
#import "WXDiffUtil.h"
@interface WXRecycleListDiffResult : NSObject
@property (nonatomic, strong, readonly) NSMutableSet<NSIndexPath *> *deleteIndexPaths;
@property (nonatomic, strong, readonly) NSMutableSet<NSIndexPath *> *insertIndexPaths;
@property (nonatomic, strong, readonly) NSMutableSet<NSIndexPath *> *reloadIndexPaths;
- (BOOL)hasChanges;
@end
@implementation WXRecycleListDiffResult
- (instancetype)initWithInsertIndexPaths:(NSMutableSet<NSIndexPath *> *)insertIndexPaths
deleteIndexPaths:(NSMutableSet<NSIndexPath *> *)deleteIndexPaths
reloadIndexPaths:(NSMutableSet<NSIndexPath *> *)reloadIndexPaths
{
if (self = [super init]) {
_insertIndexPaths = [insertIndexPaths copy];
_deleteIndexPaths = [deleteIndexPaths copy];
_reloadIndexPaths = [reloadIndexPaths copy];
}
return self;
}
- (BOOL)hasChanges
{
return _insertIndexPaths.count > 0 || _deleteIndexPaths.count > 0 || _reloadIndexPaths.count > 0;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; insert index paths: %@; delete index paths: %@; reload index paths: %@", NSStringFromClass([self class]), self, _insertIndexPaths, _deleteIndexPaths, _reloadIndexPaths];
}
@end
@interface WXRecycleListUpdateManager ()
@property (nonatomic, copy) NSArray *newerData;
@property (nonatomic, copy) NSArray *appendingData;
@property (nonatomic, copy) NSArray *olderData;
@property (nonatomic, assign) BOOL isUpdating;
@property (nonatomic, strong) NSMutableArray *completions;
@property (nonatomic, strong) NSMutableSet<NSIndexPath *> *reloadIndexPaths;
@end
@implementation WXRecycleListUpdateManager
- (instancetype)init
{
if (self = [super init]) {
_completions = [NSMutableArray array];
}
return self;
}
- (void)reload
{
[_collectionView reloadData];
}
- (void)updateWithNewData:(NSArray *)newData
oldData:(NSArray *)oldData
completion:(WXRecycleListUpdateCompletion)completion
animation:(BOOL)isAnimated
{
WXAssertMainThread();
if (!_collectionView) {
WXLogError(@"Update list with no collection view");
if (completion) {
completion(NO);
}
return;
}
self.newerData = newData;
self.appendingData = nil;
self.olderData = oldData;
if (completion) {
[_completions addObject:completion];
}
[self checkUpdates];
}
- (void)updateWithAppendingData:(NSArray *)appendingData
oldData:(NSArray *)oldData
completion:(WXRecycleListUpdateCompletion)completion
animation:(BOOL)isAnimated
{
if (!_collectionView) {
WXLogError(@"Update list with no collection view");
completion(NO);
return;
}
self.appendingData = appendingData;
self.olderData = oldData;
if (completion) {
[_completions addObject:completion];
}
[self checkUpdates];
}
- (void)checkUpdates
{
dispatch_async(dispatch_get_main_queue(), ^{
if (self.isUpdating) {
return ;
}
[self performBatchUpdates];
});
}
- (void)performBatchUpdates
{
WXAssertMainThread();
WXAssert(!self.isUpdating, @"Can not perform updates while an updating is being performed");
UICollectionView *collectionView = self.collectionView;
if (!collectionView) {
return;
}
NSArray *newData = [self.newerData copy];
NSArray *oldData = [self.olderData copy];
NSArray *appendingData = [self.appendingData copy];
//TODO use completionBlocks
// NSArray *completionBlocks = [self.completions copy];
[self cleanup];
WXDiffResult *diffResult;
if (appendingData) {
newData = [oldData arrayByAddingObjectsFromArray:appendingData];
NSIndexSet *inserts = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(oldData.count, appendingData.count)];
diffResult = [[WXDiffResult alloc] initWithInserts:inserts deletes:nil updates:nil];
} else if (newData){
diffResult = [WXDiffUtil diffWithMinimumDistance:newData oldArray:oldData];
}
WXRecycleListDiffResult *recycleListDiffResult = [self recycleListUpdatesByDiffResult:diffResult];
if (![diffResult hasChanges] && self.reloadIndexPaths.count == 0) {
return;
}
void (^updates)(void) = [^{
[self.delegate updateManager:self willUpdateData:newData];
[UIView setAnimationsEnabled:NO];
[self applyUpdateWithDiffResult:recycleListDiffResult];
} copy];
void (^completion)(BOOL) = [^(BOOL finished) {
[UIView setAnimationsEnabled:YES];
self.isUpdating = NO;
[self.delegate updateManager:self didUpdateData:newData withSuccess:finished];
[self.reloadIndexPaths removeAllObjects];
[self checkUpdates];
} copy];
self.isUpdating = YES;
if (!self.delegate || !collectionView.dataSource) {
return;
}
[collectionView performBatchUpdates:updates completion:completion];
}
- (WXRecycleListDiffResult *)recycleListUpdatesByDiffResult:(WXDiffResult *)diffResult
{
NSMutableSet<NSIndexPath *> *reloadIndexPaths = [NSMutableSet set];
NSMutableSet<NSIndexPath *> *deleteIndexPaths = [NSMutableSet set];
NSMutableSet<NSIndexPath *> *insertIndexPaths = [NSMutableSet set];
[diffResult.updates enumerateObjectsUsingBlock:^(WXDiffUpdateIndex * _Nonnull update, NSUInteger idx, BOOL * _Nonnull stop) {
NSIndexPath *reloadIndexPath = [NSIndexPath indexPathForItem:update.oldIndex inSection:0];
[reloadIndexPaths addObject:reloadIndexPath];
}];
[diffResult.inserts enumerateIndexesUsingBlock:^(NSUInteger insertIndex, BOOL * _Nonnull stop) {
NSIndexPath *insertIndexPath = [NSIndexPath indexPathForItem:insertIndex inSection:0];
[insertIndexPaths addObject:insertIndexPath];
}];
[diffResult.deletes enumerateIndexesUsingBlock:^(NSUInteger deleteIndex, BOOL * _Nonnull stop) {
NSIndexPath *deleteIndexPath = [NSIndexPath indexPathForItem:deleteIndex inSection:0];
[deleteIndexPaths addObject:deleteIndexPath];
}];
WXRecycleListDiffResult *result = [[WXRecycleListDiffResult alloc] initWithInsertIndexPaths:insertIndexPaths deleteIndexPaths:deleteIndexPaths reloadIndexPaths:reloadIndexPaths];
return result;
}
- (void)applyUpdateWithDiffResult:(WXRecycleListDiffResult *)diffResult
{
if (!_collectionView) {
return;
}
// reload index paths should not inculde delete index paths, otherwise it will cause crash:
// Assertion failure in
// -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:animator:]
NSMutableSet *reloadIndexPaths = self.reloadIndexPaths ? [[diffResult.reloadIndexPaths setByAddingObjectsFromSet:self.reloadIndexPaths] mutableCopy]: [diffResult.reloadIndexPaths mutableCopy];
[reloadIndexPaths minusSet:diffResult.deleteIndexPaths];
if (diffResult.deleteIndexPaths.count > 0) {
[_collectionView deleteItemsAtIndexPaths:[diffResult.deleteIndexPaths allObjects]];
}
if (diffResult.insertIndexPaths.count > 0) {
[_collectionView insertItemsAtIndexPaths:[diffResult.insertIndexPaths allObjects]];
}
if (reloadIndexPaths.count > 0) {
[_collectionView reloadItemsAtIndexPaths:[reloadIndexPaths allObjects]];
}
}
- (void)cleanup
{
self.newerData = nil;
self.appendingData = nil;
self.olderData = nil;
[self.completions removeAllObjects];
}
@end