mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-09-08 05:30:47 +00:00
367 lines
13 KiB
Objective-C
367 lines
13 KiB
Objective-C
//
|
|
// _ASHierarchyChangeSet.m
|
|
// AsyncDisplayKit
|
|
//
|
|
// Created by Adlai Holler on 9/29/15.
|
|
// Copyright © 2015 Facebook. All rights reserved.
|
|
//
|
|
|
|
#import "_ASHierarchyChangeSet.h"
|
|
#import "ASInternalHelpers.h"
|
|
|
|
@interface _ASHierarchySectionChange ()
|
|
- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexSet:(NSIndexSet *)indexSet animationOptions:(ASDataControllerAnimationOptions)animationOptions;
|
|
|
|
/**
|
|
On return `changes` is sorted according to the change type with changes coalesced by animationOptions
|
|
Assumes: `changes` is [_ASHierarchySectionChange] all with the same changeType
|
|
*/
|
|
+ (void)sortAndCoalesceChanges:(NSMutableArray *)changes;
|
|
|
|
/// Returns all the indexes from all the `indexSet`s of the given `_ASHierarchySectionChange` objects.
|
|
+ (NSMutableIndexSet *)allIndexesInChanges:(NSArray *)changes;
|
|
@end
|
|
|
|
@interface _ASHierarchyItemChange ()
|
|
- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexPaths:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)animationOptions presorted:(BOOL)presorted;
|
|
|
|
/**
|
|
On return `changes` is sorted according to the change type with changes coalesced by animationOptions
|
|
Assumes: `changes` is [_ASHierarchyItemChange] all with the same changeType
|
|
*/
|
|
+ (void)sortAndCoalesceChanges:(NSMutableArray *)changes ignoringChangesInSections:(NSIndexSet *)sections;
|
|
@end
|
|
|
|
@interface _ASHierarchyChangeSet ()
|
|
|
|
@property (nonatomic, strong, readonly) NSMutableArray *insertItemChanges;
|
|
@property (nonatomic, strong, readonly) NSMutableArray *deleteItemChanges;
|
|
@property (nonatomic, strong, readonly) NSMutableArray *reloadItemChanges;
|
|
@property (nonatomic, strong, readonly) NSMutableArray *insertSectionChanges;
|
|
@property (nonatomic, strong, readonly) NSMutableArray *deleteSectionChanges;
|
|
@property (nonatomic, strong, readonly) NSMutableArray *reloadSectionChanges;
|
|
|
|
@end
|
|
|
|
@implementation _ASHierarchyChangeSet
|
|
|
|
- (instancetype)init
|
|
{
|
|
self = [super init];
|
|
if (self) {
|
|
|
|
_insertItemChanges = [NSMutableArray new];
|
|
_deleteItemChanges = [NSMutableArray new];
|
|
_reloadItemChanges = [NSMutableArray new];
|
|
_insertSectionChanges = [NSMutableArray new];
|
|
_deleteSectionChanges = [NSMutableArray new];
|
|
_reloadSectionChanges = [NSMutableArray new];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
#pragma mark External API
|
|
|
|
- (void)markCompleted
|
|
{
|
|
NSAssert(!_completed, @"Attempt to mark already-completed changeset as completed.");
|
|
_completed = YES;
|
|
[self _sortAndCoalesceChangeArrays];
|
|
}
|
|
|
|
- (NSArray *)sectionChangesOfType:(_ASHierarchyChangeType)changeType
|
|
{
|
|
[self _ensureCompleted];
|
|
switch (changeType) {
|
|
case _ASHierarchyChangeTypeInsert:
|
|
return _insertSectionChanges;
|
|
case _ASHierarchyChangeTypeReload:
|
|
return _reloadSectionChanges;
|
|
case _ASHierarchyChangeTypeDelete:
|
|
return _deleteSectionChanges;
|
|
default:
|
|
NSAssert(NO, @"Request for section changes with invalid type: %lu", (long)changeType);
|
|
}
|
|
}
|
|
|
|
- (NSArray *)itemChangesOfType:(_ASHierarchyChangeType)changeType
|
|
{
|
|
[self _ensureCompleted];
|
|
switch (changeType) {
|
|
case _ASHierarchyChangeTypeInsert:
|
|
return _insertItemChanges;
|
|
case _ASHierarchyChangeTypeReload:
|
|
return _reloadItemChanges;
|
|
case _ASHierarchyChangeTypeDelete:
|
|
return _deleteItemChanges;
|
|
default:
|
|
NSAssert(NO, @"Request for item changes with invalid type: %lu", (long)changeType);
|
|
}
|
|
}
|
|
|
|
- (NSInteger)newSectionForOldSection:(NSInteger)oldSection
|
|
{
|
|
[self _ensureCompleted];
|
|
if ([_deletedSections containsIndex:oldSection]) {
|
|
return NSNotFound;
|
|
}
|
|
|
|
NSInteger indexAfterDeletes = oldSection - [_deletedSections countOfIndexesInRange:NSMakeRange(0, oldSection)];
|
|
return indexAfterDeletes + [_insertedSections countOfIndexesInRange:NSMakeRange(0, indexAfterDeletes)];
|
|
}
|
|
|
|
- (void)deleteItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options
|
|
{
|
|
[self _ensureNotCompleted];
|
|
_ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeDelete indexPaths:indexPaths animationOptions:options presorted:NO];
|
|
[_deleteItemChanges addObject:change];
|
|
}
|
|
|
|
- (void)deleteSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options
|
|
{
|
|
[self _ensureNotCompleted];
|
|
_ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeDelete indexSet:sections animationOptions:options];
|
|
[_deleteSectionChanges addObject:change];
|
|
}
|
|
|
|
- (void)insertItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options
|
|
{
|
|
[self _ensureNotCompleted];
|
|
_ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeInsert indexPaths:indexPaths animationOptions:options presorted:NO];
|
|
[_insertItemChanges addObject:change];
|
|
}
|
|
|
|
- (void)insertSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options
|
|
{
|
|
[self _ensureNotCompleted];
|
|
_ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeInsert indexSet:sections animationOptions:options];
|
|
[_insertSectionChanges addObject:change];
|
|
}
|
|
|
|
- (void)reloadItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options
|
|
{
|
|
[self _ensureNotCompleted];
|
|
_ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeReload indexPaths:indexPaths animationOptions:options presorted:NO];
|
|
[_reloadItemChanges addObject:change];
|
|
}
|
|
|
|
- (void)reloadSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options
|
|
{
|
|
[self _ensureNotCompleted];
|
|
_ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeReload indexSet:sections animationOptions:options];
|
|
[_reloadSectionChanges addObject:change];
|
|
}
|
|
|
|
#pragma mark Private
|
|
|
|
- (BOOL)_ensureNotCompleted
|
|
{
|
|
NSAssert(!_completed, @"Attempt to modify completed changeset %@", self);
|
|
return !_completed;
|
|
}
|
|
|
|
- (BOOL)_ensureCompleted
|
|
{
|
|
NSAssert(_completed, @"Attempt to process incomplete changeset %@", self);
|
|
return _completed;
|
|
}
|
|
|
|
- (void)_sortAndCoalesceChangeArrays
|
|
{
|
|
@autoreleasepool {
|
|
[_ASHierarchySectionChange sortAndCoalesceChanges:_deleteSectionChanges];
|
|
[_ASHierarchySectionChange sortAndCoalesceChanges:_insertSectionChanges];
|
|
[_ASHierarchySectionChange sortAndCoalesceChanges:_reloadSectionChanges];
|
|
|
|
_deletedSections = [[_ASHierarchySectionChange allIndexesInChanges:_deleteSectionChanges] copy];
|
|
_insertedSections = [[_ASHierarchySectionChange allIndexesInChanges:_insertSectionChanges] copy];
|
|
_reloadedSections = [[_ASHierarchySectionChange allIndexesInChanges:_reloadSectionChanges] copy];
|
|
|
|
// These are invalid old section indexes.
|
|
NSMutableIndexSet *deletedOrReloaded = [_deletedSections mutableCopy];
|
|
[deletedOrReloaded addIndexes:_reloadedSections];
|
|
|
|
// These are invalid new section indexes.
|
|
NSMutableIndexSet *insertedOrReloaded = [_insertedSections mutableCopy];
|
|
|
|
// Get the new section that each reloaded section index corresponds to.
|
|
[_reloadedSections enumerateIndexesUsingBlock:^(NSUInteger oldIndex, __unused BOOL * stop) {
|
|
NSUInteger newIndex = [self newSectionForOldSection:oldIndex];
|
|
if (newIndex != NSNotFound) {
|
|
[insertedOrReloaded addIndex:newIndex];
|
|
}
|
|
}];
|
|
|
|
// Ignore item reloads/deletes in reloaded/deleted sections.
|
|
[_ASHierarchyItemChange sortAndCoalesceChanges:_deleteItemChanges ignoringChangesInSections:deletedOrReloaded];
|
|
[_ASHierarchyItemChange sortAndCoalesceChanges:_reloadItemChanges ignoringChangesInSections:deletedOrReloaded];
|
|
|
|
// Ignore item inserts in reloaded(new)/inserted sections.
|
|
[_ASHierarchyItemChange sortAndCoalesceChanges:_insertItemChanges ignoringChangesInSections:insertedOrReloaded];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation _ASHierarchySectionChange
|
|
|
|
- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexSet:(NSIndexSet *)indexSet animationOptions:(ASDataControllerAnimationOptions)animationOptions
|
|
{
|
|
self = [super init];
|
|
if (self) {
|
|
_changeType = changeType;
|
|
_indexSet = indexSet;
|
|
_animationOptions = animationOptions;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
+ (void)sortAndCoalesceChanges:(NSMutableArray *)changes
|
|
{
|
|
if (changes.count < 1) {
|
|
return;
|
|
}
|
|
|
|
_ASHierarchyChangeType type = [changes.firstObject changeType];
|
|
|
|
// Lookup table [Int: AnimationOptions]
|
|
NSMutableDictionary *animationOptions = [NSMutableDictionary new];
|
|
|
|
// All changed indexes, sorted
|
|
NSMutableIndexSet *allIndexes = [NSMutableIndexSet new];
|
|
|
|
for (_ASHierarchySectionChange *change in changes) {
|
|
[change.indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, __unused BOOL *stop) {
|
|
animationOptions[@(idx)] = @(change.animationOptions);
|
|
}];
|
|
[allIndexes addIndexes:change.indexSet];
|
|
}
|
|
|
|
// Create new changes by grouping sorted changes by animation option
|
|
NSMutableArray *result = [NSMutableArray new];
|
|
|
|
__block ASDataControllerAnimationOptions currentOptions = 0;
|
|
NSMutableIndexSet *currentIndexes = [NSMutableIndexSet indexSet];
|
|
|
|
NSEnumerationOptions options = type == _ASHierarchyChangeTypeDelete ? NSEnumerationReverse : kNilOptions;
|
|
|
|
[allIndexes enumerateIndexesWithOptions:options usingBlock:^(NSUInteger idx, __unused BOOL * stop) {
|
|
ASDataControllerAnimationOptions options = [animationOptions[@(idx)] integerValue];
|
|
|
|
// End the previous group if needed.
|
|
if (options != currentOptions && currentIndexes.count > 0) {
|
|
_ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:type indexSet:[currentIndexes copy] animationOptions:currentOptions];
|
|
[result addObject:change];
|
|
[currentIndexes removeAllIndexes];
|
|
}
|
|
|
|
// Start a new group if needed.
|
|
if (currentIndexes.count == 0) {
|
|
currentOptions = options;
|
|
}
|
|
|
|
[currentIndexes addIndex:idx];
|
|
}];
|
|
|
|
// Finish up the last group.
|
|
if (currentIndexes.count > 0) {
|
|
_ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:type indexSet:[currentIndexes copy] animationOptions:currentOptions];
|
|
[result addObject:change];
|
|
}
|
|
|
|
[changes setArray:result];
|
|
}
|
|
|
|
+ (NSMutableIndexSet *)allIndexesInChanges:(NSArray *)changes
|
|
{
|
|
NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet];
|
|
for (_ASHierarchySectionChange *change in changes) {
|
|
[indexes addIndexes:change.indexSet];
|
|
}
|
|
return indexes;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation _ASHierarchyItemChange
|
|
|
|
- (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexPaths:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)animationOptions presorted:(BOOL)presorted
|
|
{
|
|
self = [super init];
|
|
if (self) {
|
|
_changeType = changeType;
|
|
if (presorted) {
|
|
_indexPaths = indexPaths;
|
|
} else {
|
|
SEL sorting = changeType == _ASHierarchyChangeTypeDelete ? @selector(asdk_inverseCompare:) : @selector(compare:);
|
|
_indexPaths = [indexPaths sortedArrayUsingSelector:sorting];
|
|
}
|
|
_animationOptions = animationOptions;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
+ (void)sortAndCoalesceChanges:(NSMutableArray *)changes ignoringChangesInSections:(NSIndexSet *)sections
|
|
{
|
|
if (changes.count < 1) {
|
|
return;
|
|
}
|
|
|
|
_ASHierarchyChangeType type = [changes.firstObject changeType];
|
|
|
|
// Lookup table [NSIndexPath: AnimationOptions]
|
|
NSMutableDictionary *animationOptions = [NSMutableDictionary new];
|
|
|
|
// All changed index paths, sorted
|
|
NSMutableArray *allIndexPaths = [NSMutableArray new];
|
|
|
|
NSPredicate *indexPathInValidSection = [NSPredicate predicateWithBlock:^BOOL(NSIndexPath *indexPath, __unused NSDictionary *_) {
|
|
return ![sections containsIndex:indexPath.section];
|
|
}];
|
|
for (_ASHierarchyItemChange *change in changes) {
|
|
for (NSIndexPath *indexPath in change.indexPaths) {
|
|
if ([indexPathInValidSection evaluateWithObject:indexPath]) {
|
|
animationOptions[indexPath] = @(change.animationOptions);
|
|
[allIndexPaths addObject:indexPath];
|
|
}
|
|
}
|
|
}
|
|
|
|
SEL sorting = type == _ASHierarchyChangeTypeDelete ? @selector(asdk_inverseCompare:) : @selector(compare:);
|
|
[allIndexPaths sortUsingSelector:sorting];
|
|
|
|
// Create new changes by grouping sorted changes by animation option
|
|
NSMutableArray *result = [NSMutableArray new];
|
|
|
|
ASDataControllerAnimationOptions currentOptions = 0;
|
|
NSMutableArray *currentIndexPaths = [NSMutableArray array];
|
|
|
|
for (NSIndexPath *indexPath in allIndexPaths) {
|
|
ASDataControllerAnimationOptions options = [animationOptions[indexPath] integerValue];
|
|
|
|
// End the previous group if needed.
|
|
if (options != currentOptions && currentIndexPaths.count > 0) {
|
|
_ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:type indexPaths:[currentIndexPaths copy] animationOptions:currentOptions presorted:YES];
|
|
[result addObject:change];
|
|
[currentIndexPaths removeAllObjects];
|
|
}
|
|
|
|
// Start a new group if needed.
|
|
if (currentIndexPaths.count == 0) {
|
|
currentOptions = options;
|
|
}
|
|
|
|
[currentIndexPaths addObject:indexPath];
|
|
}
|
|
|
|
// Finish up the last group.
|
|
if (currentIndexPaths.count > 0) {
|
|
_ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:type indexPaths:[currentIndexPaths copy] animationOptions:currentOptions presorted:YES];
|
|
[result addObject:change];
|
|
}
|
|
|
|
[changes setArray:result];
|
|
}
|
|
|
|
@end
|