mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-09-10 22:45:16 +00:00
- Introduce thread-safe ASEventLog - ASCollectionNode and ASTableNode share their event log with their ASDataController. The controller uses it to log change set submitting and finishing events. - ASCollectionNode and ASTableNode print their data source and delegate in their debug description.
343 lines
14 KiB
Plaintext
343 lines
14 KiB
Plaintext
//
|
|
// ASCollectionDataController.mm
|
|
// AsyncDisplayKit
|
|
//
|
|
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
|
// This source code is licensed under the BSD-style license found in the
|
|
// LICENSE file in the root directory of this source tree. An additional grant
|
|
// of patent rights can be found in the PATENTS file in the same directory.
|
|
//
|
|
|
|
#import "ASCollectionDataController.h"
|
|
|
|
#import "ASAssert.h"
|
|
#import "ASMultidimensionalArrayUtils.h"
|
|
#import "ASCellNode.h"
|
|
#import "ASDataController+Subclasses.h"
|
|
#import "ASIndexedNodeContext.h"
|
|
#import "ASSection.h"
|
|
#import "ASSectionContext.h"
|
|
|
|
//#define LOG(...) NSLog(__VA_ARGS__)
|
|
#define LOG(...)
|
|
|
|
@interface ASCollectionDataController () {
|
|
BOOL _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath;
|
|
NSInteger _nextSectionID;
|
|
NSMutableArray<ASSection *> *_sections;
|
|
NSArray<ASSection *> *_pendingSections;
|
|
}
|
|
|
|
- (id<ASCollectionDataControllerSource>)collectionDataSource;
|
|
|
|
@end
|
|
|
|
@implementation ASCollectionDataController {
|
|
NSMutableDictionary<NSString *, NSMutableArray<ASIndexedNodeContext *> *> *_pendingNodeContexts;
|
|
}
|
|
|
|
- (instancetype)initWithDataSource:(id<ASCollectionDataControllerSource>)dataSource eventLog:(ASEventLog *)eventLog
|
|
{
|
|
self = [super initWithDataSource:dataSource eventLog:eventLog];
|
|
if (self != nil) {
|
|
_pendingNodeContexts = [NSMutableDictionary dictionary];
|
|
_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath = [dataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)];
|
|
|
|
ASDisplayNodeAssertTrue(_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath || [dataSource respondsToSelector:@selector(dataController:supplementaryNodeOfKind:atIndexPath:)]);
|
|
|
|
_nextSectionID = 0;
|
|
_sections = [NSMutableArray array];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount
|
|
{
|
|
ASDisplayNodeAssertMainThread();
|
|
NSIndexSet *sections = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newSectionCount)];
|
|
|
|
[_sections removeAllObjects];
|
|
[self _populatePendingSectionsFromDataSource:sections];
|
|
|
|
for (NSString *kind in [self supplementaryKinds]) {
|
|
LOG(@"Populating elements of kind: %@", kind);
|
|
NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
|
|
[self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts];
|
|
_pendingNodeContexts[kind] = contexts;
|
|
}
|
|
}
|
|
|
|
- (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount
|
|
{
|
|
NSIndexSet *sectionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newSectionCount)];
|
|
|
|
[self applyPendingSections:sectionIndexes];
|
|
|
|
[_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray<ASIndexedNodeContext *> * _Nonnull contexts, __unused BOOL * _Nonnull stop) {
|
|
// Remove everything that existed before the reload, now that we're ready to insert replacements
|
|
NSArray *indexPaths = [self indexPathsForEditingNodesOfKind:kind];
|
|
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
|
|
|
|
NSArray *editingNodes = [self editingNodesOfKind:kind];
|
|
NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)];
|
|
[self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil];
|
|
|
|
// Insert each section
|
|
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:newSectionCount];
|
|
for (int i = 0; i < newSectionCount; i++) {
|
|
[sections addObject:[NSMutableArray array]];
|
|
}
|
|
[self insertSections:sections ofKind:kind atIndexSet:sectionIndexes completion:nil];
|
|
|
|
[self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
|
|
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
|
|
}];
|
|
}];
|
|
[_pendingNodeContexts removeAllObjects];
|
|
}
|
|
|
|
- (void)prepareForInsertSections:(NSIndexSet *)sections
|
|
{
|
|
ASDisplayNodeAssertMainThread();
|
|
[self _populatePendingSectionsFromDataSource:sections];
|
|
|
|
for (NSString *kind in [self supplementaryKinds]) {
|
|
LOG(@"Populating elements of kind: %@, for sections: %@", kind, sections);
|
|
NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
|
|
[self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts];
|
|
_pendingNodeContexts[kind] = contexts;
|
|
}
|
|
}
|
|
|
|
- (void)willInsertSections:(NSIndexSet *)sections
|
|
{
|
|
[self applyPendingSections:sections];
|
|
|
|
[_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray<ASIndexedNodeContext *> * _Nonnull contexts, BOOL * _Nonnull stop) {
|
|
NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count];
|
|
for (NSUInteger i = 0; i < sections.count; i++) {
|
|
[sectionArray addObject:[NSMutableArray array]];
|
|
}
|
|
|
|
[self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil];
|
|
[self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
|
|
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
|
|
}];
|
|
}];
|
|
[_pendingNodeContexts removeAllObjects];
|
|
}
|
|
|
|
- (void)willDeleteSections:(NSIndexSet *)sections
|
|
{
|
|
[_sections removeObjectsAtIndexes:sections];
|
|
|
|
for (NSString *kind in [self supplementaryKinds]) {
|
|
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections);
|
|
|
|
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
|
|
[self deleteSectionsOfKind:kind atIndexSet:sections completion:nil];
|
|
}
|
|
}
|
|
|
|
- (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection
|
|
{
|
|
ASSection *movedSection = [_sections objectAtIndex:section];
|
|
[_sections removeObjectAtIndex:section];
|
|
[_sections insertObject:movedSection atIndex:newSection];
|
|
|
|
NSIndexSet *sectionAsIndexSet = [NSIndexSet indexSetWithIndex:section];
|
|
for (NSString *kind in [self supplementaryKinds]) {
|
|
NSMutableArray *editingNodes = [self editingNodesOfKind:kind];
|
|
NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(editingNodes, sectionAsIndexSet);
|
|
NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths);
|
|
[self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil];
|
|
|
|
// update the section of indexpaths
|
|
NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count];
|
|
for (NSIndexPath *indexPath in indexPaths) {
|
|
NSUInteger newItem = [indexPath indexAtPosition:indexPath.length - 1];
|
|
NSIndexPath *mappedIndexPath = [NSIndexPath indexPathForItem:newItem inSection:newSection];
|
|
[updatedIndexPaths addObject:mappedIndexPath];
|
|
}
|
|
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
|
|
}
|
|
}
|
|
|
|
- (void)prepareForInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
|
|
{
|
|
ASDisplayNodeAssertMainThread();
|
|
for (NSString *kind in [self supplementaryKinds]) {
|
|
LOG(@"Populating elements of kind: %@, for index paths: %@", kind, indexPaths);
|
|
NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
|
|
[self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts];
|
|
_pendingNodeContexts[kind] = contexts;
|
|
}
|
|
}
|
|
|
|
- (void)willInsertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
|
|
{
|
|
[_pendingNodeContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray<ASIndexedNodeContext *> * _Nonnull contexts, BOOL * _Nonnull stop) {
|
|
[self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
|
|
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
|
|
}];
|
|
}];
|
|
|
|
[_pendingNodeContexts removeAllObjects];
|
|
}
|
|
|
|
- (void)prepareForDeleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
|
|
{
|
|
ASDisplayNodeAssertMainThread();
|
|
for (NSString *kind in [self supplementaryKinds]) {
|
|
NSMutableArray<ASIndexedNodeContext *> *contexts = [NSMutableArray array];
|
|
[self _populateSupplementaryNodesOfKind:kind atIndexPaths:indexPaths mutableContexts:contexts];
|
|
_pendingNodeContexts[kind] = contexts;
|
|
}
|
|
}
|
|
|
|
- (void)willDeleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
|
|
{
|
|
for (NSString *kind in [self supplementaryKinds]) {
|
|
NSArray<NSIndexPath *> *deletedIndexPaths = ASIndexPathsInMultidimensionalArrayIntersectingIndexPaths([self editingNodesOfKind:kind], indexPaths);
|
|
|
|
[self deleteNodesOfKind:kind atIndexPaths:deletedIndexPaths completion:nil];
|
|
|
|
// If any of the contexts remain after the deletion, re-insert them, e.g.
|
|
// UICollectionElementKindSectionHeader remains even if item 0 is deleted.
|
|
NSMutableArray<ASIndexedNodeContext *> *reinsertedContexts = [NSMutableArray array];
|
|
for (ASIndexedNodeContext *context in _pendingNodeContexts[kind]) {
|
|
if ([deletedIndexPaths containsObject:context.indexPath]) {
|
|
[reinsertedContexts addObject:context];
|
|
}
|
|
}
|
|
|
|
[self batchLayoutNodesFromContexts:reinsertedContexts batchCompletion:^(NSArray<ASCellNode *> *nodes, NSArray<NSIndexPath *> *indexPaths) {
|
|
[self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil];
|
|
}];
|
|
}
|
|
[_pendingNodeContexts removeAllObjects];
|
|
}
|
|
|
|
- (void)_populatePendingSectionsFromDataSource:(NSIndexSet *)sectionIndexes
|
|
{
|
|
ASDisplayNodeAssertMainThread();
|
|
|
|
NSMutableArray<ASSection *> *sections = [NSMutableArray arrayWithCapacity:sectionIndexes.count];
|
|
[sectionIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
|
|
id<ASSectionContext> context = [self.collectionDataSource dataController:self contextForSection:idx];
|
|
[sections addObject:[[ASSection alloc] initWithSectionID:_nextSectionID context:context]];
|
|
_nextSectionID++;
|
|
}];
|
|
_pendingSections = sections;
|
|
}
|
|
|
|
- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withSections:(NSIndexSet *)sections mutableContexts:(NSMutableArray<ASIndexedNodeContext *> *)contexts
|
|
{
|
|
__weak id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
|
|
|
|
[sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) {
|
|
for (NSUInteger sec = range.location; sec < NSMaxRange(range); sec++) {
|
|
NSUInteger itemCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:sec];
|
|
for (NSUInteger i = 0; i < itemCount; i++) {
|
|
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sec];
|
|
[self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environment:environment];
|
|
}
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)_populateSupplementaryNodesOfKind:(NSString *)kind atIndexPaths:(NSArray<NSIndexPath *> *)indexPaths mutableContexts:(NSMutableArray<ASIndexedNodeContext *> *)contexts
|
|
{
|
|
__weak id<ASEnvironment> environment = [self.environmentDelegate dataControllerEnvironment];
|
|
|
|
NSMutableIndexSet *sections = [NSMutableIndexSet indexSet];
|
|
for (NSIndexPath *indexPath in indexPaths) {
|
|
[sections addIndex:indexPath.section];
|
|
}
|
|
|
|
[sections enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) {
|
|
for (NSUInteger sec = range.location; sec < NSMaxRange(range); sec++) {
|
|
NSUInteger itemCount = [self.collectionDataSource dataController:self supplementaryNodesOfKind:kind inSection:sec];
|
|
for (NSUInteger i = 0; i < itemCount; i++) {
|
|
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:sec];
|
|
[self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environment:environment];
|
|
}
|
|
}
|
|
}];
|
|
}
|
|
|
|
- (void)_populateSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath mutableContexts:(NSMutableArray<ASIndexedNodeContext *> *)contexts environment:(id<ASEnvironment>)environment
|
|
{
|
|
ASCellNodeBlock supplementaryCellBlock;
|
|
if (_dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath) {
|
|
supplementaryCellBlock = [self.collectionDataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath];
|
|
} else {
|
|
ASCellNode *supplementaryNode = [self.collectionDataSource dataController:self supplementaryNodeOfKind:kind atIndexPath:indexPath];
|
|
supplementaryCellBlock = ^{ return supplementaryNode; };
|
|
}
|
|
|
|
ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
|
|
ASIndexedNodeContext *context = [[ASIndexedNodeContext alloc] initWithNodeBlock:supplementaryCellBlock
|
|
indexPath:indexPath
|
|
supplementaryElementKind:kind
|
|
constrainedSize:constrainedSize
|
|
environment:environment];
|
|
[contexts addObject:context];
|
|
}
|
|
|
|
#pragma mark - Sizing query
|
|
|
|
- (ASSizeRange)constrainedSizeForNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
if ([kind isEqualToString:ASDataControllerRowNodeKind]) {
|
|
return [super constrainedSizeForNodeOfKind:kind atIndexPath:indexPath];
|
|
} else {
|
|
ASDisplayNodeAssertMainThread();
|
|
return [self.collectionDataSource dataController:self constrainedSizeForSupplementaryNodeOfKind:kind atIndexPath:indexPath];
|
|
}
|
|
}
|
|
|
|
#pragma mark - External supplementary store and section context querying
|
|
|
|
- (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
|
|
{
|
|
ASDisplayNodeAssertMainThread();
|
|
NSArray *nodesOfKind = [self completedNodesOfKind:kind];
|
|
NSInteger section = indexPath.section;
|
|
if (section < nodesOfKind.count) {
|
|
NSArray *nodesOfKindInSection = nodesOfKind[section];
|
|
NSInteger itemIndex = indexPath.item;
|
|
if (itemIndex < nodesOfKindInSection.count) {
|
|
return nodesOfKindInSection[itemIndex];
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (id<ASSectionContext>)contextForSection:(NSInteger)section
|
|
{
|
|
ASDisplayNodeAssertMainThread();
|
|
ASDisplayNodeAssertTrue(section >= 0 && section < _sections.count);
|
|
return _sections[section].context;
|
|
}
|
|
|
|
#pragma mark - Private Helpers
|
|
|
|
- (NSArray *)supplementaryKinds
|
|
{
|
|
return [self.collectionDataSource supplementaryNodeKindsInDataController:self];
|
|
}
|
|
|
|
- (id<ASCollectionDataControllerSource>)collectionDataSource
|
|
{
|
|
return (id<ASCollectionDataControllerSource>)self.dataSource;
|
|
}
|
|
|
|
- (void)applyPendingSections:(NSIndexSet *)sectionIndexes
|
|
{
|
|
[_sections insertObjects:_pendingSections atIndexes:sectionIndexes];
|
|
_pendingSections = nil;
|
|
}
|
|
|
|
@end
|