_ASHierarchyChangeSet.mm 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885
  1. //
  2. // _ASHierarchyChangeSet.m
  3. // AsyncDisplayKit
  4. //
  5. // Created by Adlai Holler on 9/29/15.
  6. //
  7. // Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
  8. // This source code is licensed under the BSD-style license found in the
  9. // LICENSE file in the root directory of this source tree. An additional grant
  10. // of patent rights can be found in the PATENTS file in the same directory.
  11. //
  12. #import "_ASHierarchyChangeSet.h"
  13. #import "ASInternalHelpers.h"
  14. #import "NSIndexSet+ASHelpers.h"
  15. #import "ASAssert.h"
  16. #import "ASDisplayNode+Beta.h"
  17. #import "ASObjectDescriptionHelpers.h"
  18. #import <unordered_map>
  19. #import "ASDataController.h"
  20. #import "ASBaseDefines.h"
  21. // If assertions are enabled and they haven't forced us to suppress the exception,
  22. // then throw, otherwise log.
  23. #if ASDISPLAYNODE_ASSERTIONS_ENABLED
  24. #define ASFailUpdateValidation(...)\
  25. _Pragma("clang diagnostic push")\
  26. _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"")\
  27. if ([ASDisplayNode suppressesInvalidCollectionUpdateExceptions]) {\
  28. NSLog(__VA_ARGS__);\
  29. } else {\
  30. NSLog(__VA_ARGS__);\
  31. [NSException raise:ASCollectionInvalidUpdateException format:__VA_ARGS__];\
  32. }\
  33. _Pragma("clang diagnostic pop")
  34. #else
  35. #define ASFailUpdateValidation(...) NSLog(__VA_ARGS__);
  36. #endif
  37. BOOL ASHierarchyChangeTypeIsFinal(_ASHierarchyChangeType changeType) {
  38. switch (changeType) {
  39. case _ASHierarchyChangeTypeInsert:
  40. case _ASHierarchyChangeTypeDelete:
  41. return YES;
  42. default:
  43. return NO;
  44. }
  45. }
  46. NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType)
  47. {
  48. switch (changeType) {
  49. case _ASHierarchyChangeTypeInsert:
  50. return @"Insert";
  51. case _ASHierarchyChangeTypeOriginalInsert:
  52. return @"OriginalInsert";
  53. case _ASHierarchyChangeTypeDelete:
  54. return @"Delete";
  55. case _ASHierarchyChangeTypeOriginalDelete:
  56. return @"OriginalDelete";
  57. case _ASHierarchyChangeTypeReload:
  58. return @"Reload";
  59. default:
  60. return @"(invalid)";
  61. }
  62. }
  63. @interface _ASHierarchySectionChange ()
  64. - (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexSet:(NSIndexSet *)indexSet animationOptions:(ASDataControllerAnimationOptions)animationOptions;
  65. /**
  66. On return `changes` is sorted according to the change type with changes coalesced by animationOptions
  67. Assumes: `changes` all have the same changeType
  68. */
  69. + (void)sortAndCoalesceSectionChanges:(NSMutableArray<_ASHierarchySectionChange *> *)changes;
  70. /// Returns all the indexes from all the `indexSet`s of the given `_ASHierarchySectionChange` objects.
  71. + (NSMutableIndexSet *)allIndexesInSectionChanges:(NSArray *)changes;
  72. + (NSString *)smallDescriptionForSectionChanges:(NSArray<_ASHierarchySectionChange *> *)changes;
  73. @end
  74. @interface _ASHierarchyItemChange ()
  75. - (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexPaths:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)animationOptions presorted:(BOOL)presorted;
  76. /**
  77. On return `changes` is sorted according to the change type with changes coalesced by animationOptions
  78. Assumes: `changes` all have the same changeType
  79. */
  80. + (void)sortAndCoalesceItemChanges:(NSMutableArray<_ASHierarchyItemChange *> *)changes ignoringChangesInSections:(NSIndexSet *)sections;
  81. + (NSString *)smallDescriptionForItemChanges:(NSArray<_ASHierarchyItemChange *> *)changes;
  82. + (void)ensureItemChanges:(NSArray<_ASHierarchyItemChange *> *)changes ofSameType:(_ASHierarchyChangeType)changeType;
  83. @end
  84. @interface _ASHierarchyChangeSet ()
  85. @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchyItemChange *> *insertItemChanges;
  86. @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchyItemChange *> *originalInsertItemChanges;
  87. @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchyItemChange *> *deleteItemChanges;
  88. @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchyItemChange *> *originalDeleteItemChanges;
  89. @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchyItemChange *> *reloadItemChanges;
  90. @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchySectionChange *> *insertSectionChanges;
  91. @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchySectionChange *> *originalInsertSectionChanges;
  92. @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchySectionChange *> *deleteSectionChanges;
  93. @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchySectionChange *> *originalDeleteSectionChanges;
  94. @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchySectionChange *> *reloadSectionChanges;
  95. @end
  96. @implementation _ASHierarchyChangeSet {
  97. std::vector<NSInteger> _oldItemCounts;
  98. std::vector<NSInteger> _newItemCounts;
  99. void (^_completionHandler)(BOOL finished);
  100. }
  101. - (instancetype)init
  102. {
  103. ASFailUpdateValidation(@"_ASHierarchyChangeSet: -init is not supported. Call -initWithOldData:");
  104. return [self initWithOldData:std::vector<NSInteger>()];
  105. }
  106. - (instancetype)initWithOldData:(std::vector<NSInteger>)oldItemCounts
  107. {
  108. self = [super init];
  109. if (self) {
  110. _oldItemCounts = oldItemCounts;
  111. _originalInsertItemChanges = [[NSMutableArray alloc] init];
  112. _insertItemChanges = [[NSMutableArray alloc] init];
  113. _originalDeleteItemChanges = [[NSMutableArray alloc] init];
  114. _deleteItemChanges = [[NSMutableArray alloc] init];
  115. _reloadItemChanges = [[NSMutableArray alloc] init];
  116. _originalInsertSectionChanges = [[NSMutableArray alloc] init];
  117. _insertSectionChanges = [[NSMutableArray alloc] init];
  118. _originalDeleteSectionChanges = [[NSMutableArray alloc] init];
  119. _deleteSectionChanges = [[NSMutableArray alloc] init];
  120. _reloadSectionChanges = [[NSMutableArray alloc] init];
  121. }
  122. return self;
  123. }
  124. #pragma mark External API
  125. - (void (^)(BOOL finished))completionHandler
  126. {
  127. void (^completionHandler)(BOOL) = _completionHandler;
  128. _completionHandler = nil;
  129. return completionHandler;
  130. }
  131. - (void)addCompletionHandler:(void (^)(BOOL))completion
  132. {
  133. [self _ensureNotCompleted];
  134. if (completion == nil) {
  135. return;
  136. }
  137. void (^oldCompletionHandler)(BOOL finished) = _completionHandler;
  138. _completionHandler = ^(BOOL finished) {
  139. if (oldCompletionHandler != nil) {
  140. oldCompletionHandler(finished);
  141. }
  142. completion(finished);
  143. };
  144. }
  145. - (void)markCompletedWithNewItemCounts:(std::vector<NSInteger>)newItemCounts
  146. {
  147. NSAssert(!_completed, @"Attempt to mark already-completed changeset as completed.");
  148. _completed = YES;
  149. _newItemCounts = newItemCounts;
  150. [self _sortAndCoalesceChangeArrays];
  151. [self _validateUpdate];
  152. }
  153. - (NSArray *)sectionChangesOfType:(_ASHierarchyChangeType)changeType
  154. {
  155. [self _ensureCompleted];
  156. switch (changeType) {
  157. case _ASHierarchyChangeTypeInsert:
  158. return _insertSectionChanges;
  159. case _ASHierarchyChangeTypeReload:
  160. return _reloadSectionChanges;
  161. case _ASHierarchyChangeTypeDelete:
  162. return _deleteSectionChanges;
  163. case _ASHierarchyChangeTypeOriginalDelete:
  164. return _originalDeleteSectionChanges;
  165. case _ASHierarchyChangeTypeOriginalInsert:
  166. return _originalInsertSectionChanges;
  167. default:
  168. NSAssert(NO, @"Request for section changes with invalid type: %lu", (long)changeType);
  169. return nil;
  170. }
  171. }
  172. - (NSArray *)itemChangesOfType:(_ASHierarchyChangeType)changeType
  173. {
  174. [self _ensureCompleted];
  175. switch (changeType) {
  176. case _ASHierarchyChangeTypeInsert:
  177. return _insertItemChanges;
  178. case _ASHierarchyChangeTypeReload:
  179. return _reloadItemChanges;
  180. case _ASHierarchyChangeTypeDelete:
  181. return _deleteItemChanges;
  182. case _ASHierarchyChangeTypeOriginalInsert:
  183. return _originalInsertItemChanges;
  184. case _ASHierarchyChangeTypeOriginalDelete:
  185. return _originalDeleteItemChanges;
  186. default:
  187. NSAssert(NO, @"Request for item changes with invalid type: %lu", (long)changeType);
  188. return nil;
  189. }
  190. }
  191. - (NSIndexSet *)indexesForItemChangesOfType:(_ASHierarchyChangeType)changeType inSection:(NSUInteger)section
  192. {
  193. [self _ensureCompleted];
  194. NSMutableIndexSet *result = [NSMutableIndexSet indexSet];
  195. for (_ASHierarchyItemChange *change in [self itemChangesOfType:changeType]) {
  196. [result addIndexes:[NSIndexSet as_indexSetFromIndexPaths:change.indexPaths inSection:section]];
  197. }
  198. return result;
  199. }
  200. - (NSUInteger)newSectionForOldSection:(NSUInteger)oldSection
  201. {
  202. ASDisplayNodeAssertNotNil(_deletedSections, @"Cannot call %@ before `markCompleted` returns.", NSStringFromSelector(_cmd));
  203. ASDisplayNodeAssertNotNil(_insertedSections, @"Cannot call %@ before `markCompleted` returns.", NSStringFromSelector(_cmd));
  204. [self _ensureCompleted];
  205. if ([_deletedSections containsIndex:oldSection]) {
  206. return NSNotFound;
  207. }
  208. NSUInteger newIndex = oldSection - [_deletedSections countOfIndexesInRange:NSMakeRange(0, oldSection)];
  209. newIndex += [_insertedSections as_indexChangeByInsertingItemsBelowIndex:newIndex];
  210. return newIndex;
  211. }
  212. - (void)deleteItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options
  213. {
  214. [self _ensureNotCompleted];
  215. _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeOriginalDelete indexPaths:indexPaths animationOptions:options presorted:NO];
  216. [_originalDeleteItemChanges addObject:change];
  217. }
  218. - (void)deleteSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options
  219. {
  220. [self _ensureNotCompleted];
  221. _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeOriginalDelete indexSet:sections animationOptions:options];
  222. [_originalDeleteSectionChanges addObject:change];
  223. }
  224. - (void)insertItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options
  225. {
  226. [self _ensureNotCompleted];
  227. _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeOriginalInsert indexPaths:indexPaths animationOptions:options presorted:NO];
  228. [_originalInsertItemChanges addObject:change];
  229. }
  230. - (void)insertSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options
  231. {
  232. [self _ensureNotCompleted];
  233. _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeOriginalInsert indexSet:sections animationOptions:options];
  234. [_originalInsertSectionChanges addObject:change];
  235. }
  236. - (void)reloadItems:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)options
  237. {
  238. [self _ensureNotCompleted];
  239. _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeReload indexPaths:indexPaths animationOptions:options presorted:NO];
  240. [_reloadItemChanges addObject:change];
  241. }
  242. - (void)reloadSections:(NSIndexSet *)sections animationOptions:(ASDataControllerAnimationOptions)options
  243. {
  244. [self _ensureNotCompleted];
  245. _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeReload indexSet:sections animationOptions:options];
  246. [_reloadSectionChanges addObject:change];
  247. }
  248. - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath animationOptions:(ASDataControllerAnimationOptions)options
  249. {
  250. /**
  251. * TODO: Proper move implementation.
  252. */
  253. [self deleteItems:@[ indexPath ] animationOptions:options];
  254. [self insertItems:@[ newIndexPath ] animationOptions:options];
  255. }
  256. - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection animationOptions:(ASDataControllerAnimationOptions)options
  257. {
  258. /**
  259. * TODO: Proper move implementation.
  260. */
  261. [self deleteSections:[NSIndexSet indexSetWithIndex:section] animationOptions:options];
  262. [self insertSections:[NSIndexSet indexSetWithIndex:newSection] animationOptions:options];
  263. }
  264. #pragma mark Private
  265. - (BOOL)_ensureNotCompleted
  266. {
  267. NSAssert(!_completed, @"Attempt to modify completed changeset %@", self);
  268. return !_completed;
  269. }
  270. - (BOOL)_ensureCompleted
  271. {
  272. NSAssert(_completed, @"Attempt to process incomplete changeset %@", self);
  273. return _completed;
  274. }
  275. - (void)_sortAndCoalesceChangeArrays
  276. {
  277. @autoreleasepool {
  278. // Split reloaded sections into [delete(oldIndex), insert(newIndex)]
  279. // Give these their "pre-reloads" values. Once we add in the reloads we'll re-process them.
  280. _deletedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_originalDeleteSectionChanges];
  281. _insertedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_originalInsertSectionChanges];
  282. for (_ASHierarchySectionChange *originalDeleteSectionChange in _originalDeleteSectionChanges) {
  283. [_deleteSectionChanges addObject:[originalDeleteSectionChange changeByFinalizingType]];
  284. }
  285. for (_ASHierarchySectionChange *originalInsertSectionChange in _originalInsertSectionChanges) {
  286. [_insertSectionChanges addObject:[originalInsertSectionChange changeByFinalizingType]];
  287. }
  288. for (_ASHierarchySectionChange *change in _reloadSectionChanges) {
  289. NSIndexSet *newSections = [change.indexSet as_indexesByMapping:^(NSUInteger idx) {
  290. NSUInteger newSec = [self newSectionForOldSection:idx];
  291. ASDisplayNodeAssert(newSec != NSNotFound, @"Request to reload and delete same section %tu", idx);
  292. return newSec;
  293. }];
  294. _ASHierarchySectionChange *deleteChange = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeDelete indexSet:change.indexSet animationOptions:change.animationOptions];
  295. [_deleteSectionChanges addObject:deleteChange];
  296. _ASHierarchySectionChange *insertChange = [[_ASHierarchySectionChange alloc] initWithChangeType:_ASHierarchyChangeTypeInsert indexSet:newSections animationOptions:change.animationOptions];
  297. [_insertSectionChanges addObject:insertChange];
  298. }
  299. [_ASHierarchySectionChange sortAndCoalesceSectionChanges:_deleteSectionChanges];
  300. [_ASHierarchySectionChange sortAndCoalesceSectionChanges:_insertSectionChanges];
  301. _deletedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_deleteSectionChanges];
  302. _insertedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_insertSectionChanges];
  303. // Split reloaded items into [delete(oldIndexPath), insert(newIndexPath)]
  304. for (_ASHierarchyItemChange *originalDeleteItemChange in _originalDeleteItemChanges) {
  305. [_deleteItemChanges addObject:[originalDeleteItemChange changeByFinalizingType]];
  306. }
  307. for (_ASHierarchyItemChange *originalInsertItemChange in _originalInsertItemChanges) {
  308. [_insertItemChanges addObject:[originalInsertItemChange changeByFinalizingType]];
  309. }
  310. [_ASHierarchyItemChange ensureItemChanges:_insertItemChanges ofSameType:_ASHierarchyChangeTypeInsert];
  311. NSDictionary *insertedIndexPathsMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_insertItemChanges];
  312. [_ASHierarchyItemChange ensureItemChanges:_deleteItemChanges ofSameType:_ASHierarchyChangeTypeDelete];
  313. NSDictionary *deletedIndexPathsMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_deleteItemChanges];
  314. for (_ASHierarchyItemChange *change in _reloadItemChanges) {
  315. NSAssert(change.changeType == _ASHierarchyChangeTypeReload, @"It must be a reload change to be in here");
  316. NSMutableArray *newIndexPaths = [NSMutableArray arrayWithCapacity:change.indexPaths.count];
  317. // Every indexPaths in the change need to update its section and/or row
  318. // depending on all the deletions and insertions
  319. // For reference, when batching reloads/deletes/inserts:
  320. // - delete/reload indexPaths that are passed in should all be their current indexPaths
  321. // - insert indexPaths that are passed in should all be their future indexPaths after deletions
  322. for (NSIndexPath *indexPath in change.indexPaths) {
  323. NSUInteger section = [self newSectionForOldSection:indexPath.section];
  324. NSUInteger item = indexPath.item;
  325. // Update row number based on deletions that are above the current row in the current section
  326. NSIndexSet *indicesDeletedInSection = deletedIndexPathsMap[@(indexPath.section)];
  327. item -= [indicesDeletedInSection countOfIndexesInRange:NSMakeRange(0, item)];
  328. // Update row number based on insertions that are above the current row in the future section
  329. NSIndexSet *indicesInsertedInSection = insertedIndexPathsMap[@(section)];
  330. item += [indicesInsertedInSection as_indexChangeByInsertingItemsBelowIndex:item];
  331. NSIndexPath *newIndexPath = [NSIndexPath indexPathForItem:item inSection:section];
  332. [newIndexPaths addObject:newIndexPath];
  333. }
  334. // All reload changes are translated into deletes and inserts
  335. // We delete the items that needs reload together with other deleted items, at their original index
  336. _ASHierarchyItemChange *deleteItemChangeFromReloadChange = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeDelete indexPaths:change.indexPaths animationOptions:change.animationOptions presorted:NO];
  337. [_deleteItemChanges addObject:deleteItemChangeFromReloadChange];
  338. // We insert the items that needs reload together with other inserted items, at their future index
  339. _ASHierarchyItemChange *insertItemChangeFromReloadChange = [[_ASHierarchyItemChange alloc] initWithChangeType:_ASHierarchyChangeTypeInsert indexPaths:newIndexPaths animationOptions:change.animationOptions presorted:NO];
  340. [_insertItemChanges addObject:insertItemChangeFromReloadChange];
  341. }
  342. // Ignore item deletes in reloaded/deleted sections.
  343. [_ASHierarchyItemChange sortAndCoalesceItemChanges:_deleteItemChanges ignoringChangesInSections:_deletedSections];
  344. // Ignore item inserts in reloaded(new)/inserted sections.
  345. [_ASHierarchyItemChange sortAndCoalesceItemChanges:_insertItemChanges ignoringChangesInSections:_insertedSections];
  346. }
  347. }
  348. - (void)_validateUpdate
  349. {
  350. NSIndexSet *allReloadedSections = [_ASHierarchySectionChange allIndexesInSectionChanges:_reloadSectionChanges];
  351. NSInteger newSectionCount = _newItemCounts.size();
  352. NSInteger oldSectionCount = _oldItemCounts.size();
  353. NSInteger insertedSectionCount = _insertedSections.count;
  354. NSInteger deletedSectionCount = _deletedSections.count;
  355. // Assert that the new section count is correct.
  356. if (newSectionCount != oldSectionCount + insertedSectionCount - deletedSectionCount) {
  357. ASFailUpdateValidation(@"Invalid number of sections. The number of sections after the update (%zd) must be equal to the number of sections before the update (%zd) plus or minus the number of sections inserted or deleted (%tu inserted, %tu deleted)", newSectionCount, oldSectionCount, insertedSectionCount, deletedSectionCount);
  358. return;
  359. }
  360. // Assert that no invalid deletes/reloads happened.
  361. NSInteger invalidSectionDelete = NSNotFound;
  362. if (oldSectionCount == 0) {
  363. invalidSectionDelete = _deletedSections.firstIndex;
  364. } else {
  365. invalidSectionDelete = [_deletedSections indexGreaterThanIndex:oldSectionCount - 1];
  366. }
  367. if (invalidSectionDelete != NSNotFound) {
  368. ASFailUpdateValidation(@"Attempt to delete section %zd but there are only %zd sections before the update.", invalidSectionDelete, oldSectionCount);
  369. return;
  370. }
  371. for (_ASHierarchyItemChange *change in _deleteItemChanges) {
  372. for (NSIndexPath *indexPath in change.indexPaths) {
  373. // Assert that item delete happened in a valid section.
  374. NSInteger section = indexPath.section;
  375. NSInteger item = indexPath.item;
  376. if (section >= oldSectionCount) {
  377. ASFailUpdateValidation(@"Attempt to delete item %zd from section %zd, but there are only %zd sections before the update.", item, section, oldSectionCount);
  378. return;
  379. }
  380. // Assert that item delete happened to a valid item.
  381. NSInteger oldItemCount = _oldItemCounts[section];
  382. if (item >= oldItemCount) {
  383. ASFailUpdateValidation(@"Attempt to delete item %zd from section %zd, which only contains %zd items before the update.", item, section, oldItemCount);
  384. return;
  385. }
  386. }
  387. }
  388. for (_ASHierarchyItemChange *change in _insertItemChanges) {
  389. for (NSIndexPath *indexPath in change.indexPaths) {
  390. NSInteger section = indexPath.section;
  391. NSInteger item = indexPath.item;
  392. // Assert that item insert happened in a valid section.
  393. if (section >= newSectionCount) {
  394. ASFailUpdateValidation(@"Attempt to insert item %zd into section %zd, but there are only %zd sections after the update.", item, section, newSectionCount);
  395. return;
  396. }
  397. // Assert that item delete happened to a valid item.
  398. NSInteger newItemCount = _newItemCounts[section];
  399. if (item >= newItemCount) {
  400. ASFailUpdateValidation(@"Attempt to insert item %zd into section %zd, which only contains %zd items after the update.", item, section, newItemCount);
  401. return;
  402. }
  403. }
  404. }
  405. // Assert that no sections were inserted out of bounds.
  406. NSInteger invalidSectionInsert = NSNotFound;
  407. if (newSectionCount == 0) {
  408. invalidSectionInsert = _insertedSections.firstIndex;
  409. } else {
  410. invalidSectionInsert = [_insertedSections indexGreaterThanIndex:newSectionCount - 1];
  411. }
  412. if (invalidSectionInsert != NSNotFound) {
  413. ASFailUpdateValidation(@"Attempt to insert section %zd but there are only %zd sections after the update.", invalidSectionInsert, newSectionCount);
  414. return;
  415. }
  416. for (NSUInteger oldSection = 0; oldSection < oldSectionCount; oldSection++) {
  417. NSInteger oldItemCount = _oldItemCounts[oldSection];
  418. // If section was reloaded, ignore.
  419. if ([allReloadedSections containsIndex:oldSection]) {
  420. continue;
  421. }
  422. // If section was deleted, ignore.
  423. NSUInteger newSection = [self newSectionForOldSection:oldSection];
  424. if (newSection == NSNotFound) {
  425. continue;
  426. }
  427. NSIndexSet *originalInsertedItems = [self indexesForItemChangesOfType:_ASHierarchyChangeTypeOriginalInsert inSection:newSection];
  428. NSIndexSet *originalDeletedItems = [self indexesForItemChangesOfType:_ASHierarchyChangeTypeOriginalDelete inSection:oldSection];
  429. NSIndexSet *reloadedItems = [self indexesForItemChangesOfType:_ASHierarchyChangeTypeReload inSection:oldSection];
  430. // Assert that no reloaded items were deleted.
  431. NSInteger deletedReloadedItem = [originalDeletedItems as_intersectionWithIndexes:reloadedItems].firstIndex;
  432. if (deletedReloadedItem != NSNotFound) {
  433. ASFailUpdateValidation(@"Attempt to delete and reload the same item at index path %@", [NSIndexPath indexPathForItem:deletedReloadedItem inSection:oldSection]);
  434. return;
  435. }
  436. // Assert that the new item count is correct.
  437. NSInteger newItemCount = _newItemCounts[newSection];
  438. NSInteger insertedItemCount = originalInsertedItems.count;
  439. NSInteger deletedItemCount = originalDeletedItems.count;
  440. if (newItemCount != oldItemCount + insertedItemCount - deletedItemCount) {
  441. ASFailUpdateValidation(@"Invalid number of items in section %zd. The number of items after the update (%zd) must be equal to the number of items before the update (%zd) plus or minus the number of items inserted or deleted (%zd inserted, %zd deleted).", oldSection, newItemCount, oldItemCount, insertedItemCount, deletedItemCount);
  442. return;
  443. }
  444. }
  445. }
  446. #pragma mark - Debugging (Private)
  447. - (NSString *)description
  448. {
  449. return ASObjectDescriptionMake(self, [self propertiesForDescription]);
  450. }
  451. - (NSString *)debugDescription
  452. {
  453. return ASObjectDescriptionMake(self, [self propertiesForDebugDescription]);
  454. }
  455. - (NSMutableArray<NSDictionary *> *)propertiesForDescription
  456. {
  457. NSMutableArray<NSDictionary *> *result = [NSMutableArray array];
  458. if (_reloadSectionChanges.count > 0) {
  459. [result addObject:@{ @"reloadSections" : [_ASHierarchySectionChange smallDescriptionForSectionChanges:_reloadSectionChanges] }];
  460. }
  461. if (_reloadItemChanges.count > 0) {
  462. [result addObject:@{ @"reloadItems" : [_ASHierarchyItemChange smallDescriptionForItemChanges:_reloadItemChanges] }];
  463. }
  464. if (_originalDeleteSectionChanges.count > 0) {
  465. [result addObject:@{ @"deleteSections" : [_ASHierarchySectionChange smallDescriptionForSectionChanges:_originalDeleteSectionChanges] }];
  466. }
  467. if (_originalDeleteItemChanges.count > 0) {
  468. [result addObject:@{ @"deleteItems" : [_ASHierarchyItemChange smallDescriptionForItemChanges:_originalDeleteItemChanges] }];
  469. }
  470. if (_originalInsertSectionChanges.count > 0) {
  471. [result addObject:@{ @"insertSections" : [_ASHierarchySectionChange smallDescriptionForSectionChanges:_originalInsertSectionChanges] }];
  472. }
  473. if (_originalInsertItemChanges.count > 0) {
  474. [result addObject:@{ @"insertItems" : [_ASHierarchyItemChange smallDescriptionForItemChanges:_originalInsertItemChanges] }];
  475. }
  476. return result;
  477. }
  478. - (NSMutableArray<NSDictionary *> *)propertiesForDebugDescription
  479. {
  480. return [self propertiesForDescription];
  481. }
  482. @end
  483. @implementation _ASHierarchySectionChange
  484. - (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexSet:(NSIndexSet *)indexSet animationOptions:(ASDataControllerAnimationOptions)animationOptions
  485. {
  486. self = [super init];
  487. if (self) {
  488. ASDisplayNodeAssert(indexSet.count > 0, @"Request to create _ASHierarchySectionChange with no sections!");
  489. _changeType = changeType;
  490. _indexSet = indexSet;
  491. _animationOptions = animationOptions;
  492. }
  493. return self;
  494. }
  495. - (_ASHierarchySectionChange *)changeByFinalizingType
  496. {
  497. _ASHierarchyChangeType newType;
  498. switch (_changeType) {
  499. case _ASHierarchyChangeTypeOriginalInsert:
  500. newType = _ASHierarchyChangeTypeInsert;
  501. break;
  502. case _ASHierarchyChangeTypeOriginalDelete:
  503. newType = _ASHierarchyChangeTypeDelete;
  504. break;
  505. default:
  506. ASFailUpdateValidation(@"Attempt to finalize section change of invalid type %@.", NSStringFromASHierarchyChangeType(_changeType));
  507. return self;
  508. }
  509. return [[_ASHierarchySectionChange alloc] initWithChangeType:newType indexSet:_indexSet animationOptions:_animationOptions];
  510. }
  511. + (void)sortAndCoalesceSectionChanges:(NSMutableArray<_ASHierarchySectionChange *> *)changes
  512. {
  513. _ASHierarchySectionChange *firstChange = changes.firstObject;
  514. if (firstChange == nil) {
  515. return;
  516. }
  517. _ASHierarchyChangeType type = [firstChange changeType];
  518. ASDisplayNodeAssert(ASHierarchyChangeTypeIsFinal(type), @"Attempt to sort and coalesce section changes of intermediary type %@. Why?", NSStringFromASHierarchyChangeType(type));
  519. // Lookup table [Int: AnimationOptions]
  520. __block std::unordered_map<NSUInteger, ASDataControllerAnimationOptions> animationOptions;
  521. // All changed indexes
  522. NSMutableIndexSet *allIndexes = [NSMutableIndexSet new];
  523. for (_ASHierarchySectionChange *change in changes) {
  524. ASDataControllerAnimationOptions options = change.animationOptions;
  525. NSIndexSet *indexes = change.indexSet;
  526. [indexes enumerateRangesUsingBlock:^(NSRange range, BOOL * _Nonnull stop) {
  527. for (NSUInteger i = range.location; i < NSMaxRange(range); i++) {
  528. animationOptions[i] = options;
  529. }
  530. }];
  531. [allIndexes addIndexes:indexes];
  532. }
  533. // Create new changes by grouping sorted changes by animation option
  534. NSMutableArray *result = [[NSMutableArray alloc] init];
  535. __block ASDataControllerAnimationOptions currentOptions = 0;
  536. NSMutableIndexSet *currentIndexes = [NSMutableIndexSet indexSet];
  537. BOOL reverse = type == _ASHierarchyChangeTypeDelete || type == _ASHierarchyChangeTypeOriginalDelete;
  538. NSEnumerationOptions options = reverse ? NSEnumerationReverse : kNilOptions;
  539. [allIndexes enumerateRangesWithOptions:options usingBlock:^(NSRange range, BOOL * _Nonnull stop) {
  540. NSInteger increment = reverse ? -1 : 1;
  541. NSUInteger start = reverse ? NSMaxRange(range) - 1 : range.location;
  542. NSInteger limit = reverse ? range.location - 1 : NSMaxRange(range);
  543. for (NSInteger i = start; i != limit; i += increment) {
  544. ASDataControllerAnimationOptions options = animationOptions[i];
  545. // End the previous group if needed.
  546. if (options != currentOptions && currentIndexes.count > 0) {
  547. _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:type indexSet:[currentIndexes copy] animationOptions:currentOptions];
  548. [result addObject:change];
  549. [currentIndexes removeAllIndexes];
  550. }
  551. // Start a new group if needed.
  552. if (currentIndexes.count == 0) {
  553. currentOptions = options;
  554. }
  555. [currentIndexes addIndex:i];
  556. }
  557. }];
  558. // Finish up the last group.
  559. if (currentIndexes.count > 0) {
  560. _ASHierarchySectionChange *change = [[_ASHierarchySectionChange alloc] initWithChangeType:type indexSet:[currentIndexes copy] animationOptions:currentOptions];
  561. [result addObject:change];
  562. }
  563. [changes setArray:result];
  564. }
  565. + (NSMutableIndexSet *)allIndexesInSectionChanges:(NSArray<_ASHierarchySectionChange *> *)changes
  566. {
  567. NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet];
  568. for (_ASHierarchySectionChange *change in changes) {
  569. [indexes addIndexes:change.indexSet];
  570. }
  571. return indexes;
  572. }
  573. #pragma mark - Debugging (Private)
  574. + (NSString *)smallDescriptionForSectionChanges:(NSArray<_ASHierarchySectionChange *> *)changes
  575. {
  576. NSMutableIndexSet *unionIndexSet = [NSMutableIndexSet indexSet];
  577. for (_ASHierarchySectionChange *change in changes) {
  578. [unionIndexSet addIndexes:change.indexSet];
  579. }
  580. return [unionIndexSet as_smallDescription];
  581. }
  582. - (NSString *)description
  583. {
  584. return ASObjectDescriptionMake(self, [self propertiesForDescription]);
  585. }
  586. - (NSString *)debugDescription
  587. {
  588. return ASObjectDescriptionMake(self, [self propertiesForDebugDescription]);
  589. }
  590. - (NSString *)smallDescription
  591. {
  592. return [self.indexSet as_smallDescription];
  593. }
  594. - (NSMutableArray<NSDictionary *> *)propertiesForDescription
  595. {
  596. NSMutableArray<NSDictionary *> *result = [NSMutableArray array];
  597. [result addObject:@{ @"indexes" : [self.indexSet as_smallDescription] }];
  598. return result;
  599. }
  600. - (NSMutableArray<NSDictionary *> *)propertiesForDebugDescription
  601. {
  602. NSMutableArray<NSDictionary *> *result = [NSMutableArray array];
  603. [result addObject:@{ @"anim" : @(_animationOptions) }];
  604. [result addObject:@{ @"type" : NSStringFromASHierarchyChangeType(_changeType) }];
  605. [result addObject:@{ @"indexes" : self.indexSet }];
  606. return result;
  607. }
  608. @end
  609. @implementation _ASHierarchyItemChange
  610. - (instancetype)initWithChangeType:(_ASHierarchyChangeType)changeType indexPaths:(NSArray *)indexPaths animationOptions:(ASDataControllerAnimationOptions)animationOptions presorted:(BOOL)presorted
  611. {
  612. self = [super init];
  613. if (self) {
  614. ASDisplayNodeAssert(indexPaths.count > 0, @"Request to create _ASHierarchyItemChange with no items!");
  615. _changeType = changeType;
  616. if (presorted) {
  617. _indexPaths = indexPaths;
  618. } else {
  619. SEL sorting = changeType == _ASHierarchyChangeTypeDelete ? @selector(asdk_inverseCompare:) : @selector(compare:);
  620. _indexPaths = [indexPaths sortedArrayUsingSelector:sorting];
  621. }
  622. _animationOptions = animationOptions;
  623. }
  624. return self;
  625. }
  626. // Create a mapping out of changes indexPaths to a {@section : [indexSet]} fashion
  627. // e.g. changes: (0 - 0), (0 - 1), (2 - 5)
  628. // will become: {@0 : [0, 1], @2 : [5]}
  629. + (NSDictionary *)sectionToIndexSetMapFromChanges:(NSArray<_ASHierarchyItemChange *> *)changes
  630. {
  631. NSMutableDictionary *sectionToIndexSetMap = [NSMutableDictionary dictionary];
  632. for (_ASHierarchyItemChange *change in changes) {
  633. for (NSIndexPath *indexPath in change.indexPaths) {
  634. NSNumber *sectionKey = @(indexPath.section);
  635. NSMutableIndexSet *indexSet = sectionToIndexSetMap[sectionKey];
  636. if (indexSet) {
  637. [indexSet addIndex:indexPath.item];
  638. } else {
  639. indexSet = [NSMutableIndexSet indexSetWithIndex:indexPath.item];
  640. sectionToIndexSetMap[sectionKey] = indexSet;
  641. }
  642. }
  643. }
  644. return sectionToIndexSetMap;
  645. }
  646. + (void)ensureItemChanges:(NSArray<_ASHierarchyItemChange *> *)changes ofSameType:(_ASHierarchyChangeType)changeType
  647. {
  648. #if ASDISPLAYNODE_ASSERTIONS_ENABLED
  649. for (_ASHierarchyItemChange *change in changes) {
  650. NSAssert(change.changeType == changeType, @"The map we created must all be of the same changeType as of now");
  651. }
  652. #endif
  653. }
  654. - (_ASHierarchyItemChange *)changeByFinalizingType
  655. {
  656. _ASHierarchyChangeType newType;
  657. switch (_changeType) {
  658. case _ASHierarchyChangeTypeOriginalInsert:
  659. newType = _ASHierarchyChangeTypeInsert;
  660. break;
  661. case _ASHierarchyChangeTypeOriginalDelete:
  662. newType = _ASHierarchyChangeTypeDelete;
  663. break;
  664. default:
  665. ASFailUpdateValidation(@"Attempt to finalize item change of invalid type %@.", NSStringFromASHierarchyChangeType(_changeType));
  666. return self;
  667. }
  668. return [[_ASHierarchyItemChange alloc] initWithChangeType:newType indexPaths:_indexPaths animationOptions:_animationOptions presorted:YES];
  669. }
  670. + (void)sortAndCoalesceItemChanges:(NSMutableArray<_ASHierarchyItemChange *> *)changes ignoringChangesInSections:(NSIndexSet *)ignoredSections
  671. {
  672. if (changes.count < 1) {
  673. return;
  674. }
  675. _ASHierarchyChangeType type = [changes.firstObject changeType];
  676. ASDisplayNodeAssert(ASHierarchyChangeTypeIsFinal(type), @"Attempt to sort and coalesce item changes of intermediary type %@. Why?", NSStringFromASHierarchyChangeType(type));
  677. // Lookup table [NSIndexPath: AnimationOptions]
  678. NSMutableDictionary *animationOptions = [NSMutableDictionary new];
  679. // All changed index paths, sorted
  680. NSMutableArray *allIndexPaths = [[NSMutableArray alloc] init];
  681. for (_ASHierarchyItemChange *change in changes) {
  682. for (NSIndexPath *indexPath in change.indexPaths) {
  683. if (![ignoredSections containsIndex:indexPath.section]) {
  684. animationOptions[indexPath] = @(change.animationOptions);
  685. [allIndexPaths addObject:indexPath];
  686. }
  687. }
  688. }
  689. SEL sorting = type == _ASHierarchyChangeTypeDelete ? @selector(asdk_inverseCompare:) : @selector(compare:);
  690. [allIndexPaths sortUsingSelector:sorting];
  691. // Create new changes by grouping sorted changes by animation option
  692. NSMutableArray *result = [[NSMutableArray alloc] init];
  693. ASDataControllerAnimationOptions currentOptions = 0;
  694. NSMutableArray *currentIndexPaths = [NSMutableArray array];
  695. for (NSIndexPath *indexPath in allIndexPaths) {
  696. ASDataControllerAnimationOptions options = [animationOptions[indexPath] integerValue];
  697. // End the previous group if needed.
  698. if (options != currentOptions && currentIndexPaths.count > 0) {
  699. _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:type indexPaths:[currentIndexPaths copy] animationOptions:currentOptions presorted:YES];
  700. [result addObject:change];
  701. [currentIndexPaths removeAllObjects];
  702. }
  703. // Start a new group if needed.
  704. if (currentIndexPaths.count == 0) {
  705. currentOptions = options;
  706. }
  707. [currentIndexPaths addObject:indexPath];
  708. }
  709. // Finish up the last group.
  710. if (currentIndexPaths.count > 0) {
  711. _ASHierarchyItemChange *change = [[_ASHierarchyItemChange alloc] initWithChangeType:type indexPaths:[currentIndexPaths copy] animationOptions:currentOptions presorted:YES];
  712. [result addObject:change];
  713. }
  714. [changes setArray:result];
  715. }
  716. #pragma mark - Debugging (Private)
  717. + (NSString *)smallDescriptionForItemChanges:(NSArray<_ASHierarchyItemChange *> *)changes
  718. {
  719. NSDictionary *map = [self sectionToIndexSetMapFromChanges:changes];
  720. NSMutableString *str = [NSMutableString stringWithString:@"{ "];
  721. [map enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull section, NSIndexSet * _Nonnull indexSet, BOOL * _Nonnull stop) {
  722. [str appendFormat:@"@%lu : %@ ", (long)section.integerValue, [indexSet as_smallDescription]];
  723. }];
  724. [str appendString:@"}"];
  725. return str;
  726. }
  727. - (NSString *)description
  728. {
  729. return ASObjectDescriptionMake(self, [self propertiesForDescription]);
  730. }
  731. - (NSString *)debugDescription
  732. {
  733. return ASObjectDescriptionMake(self, [self propertiesForDebugDescription]);
  734. }
  735. - (NSMutableArray<NSDictionary *> *)propertiesForDescription
  736. {
  737. NSMutableArray<NSDictionary *> *result = [NSMutableArray array];
  738. [result addObject:@{ @"indexPaths" : self.indexPaths }];
  739. return result;
  740. }
  741. - (NSMutableArray<NSDictionary *> *)propertiesForDebugDescription
  742. {
  743. NSMutableArray<NSDictionary *> *result = [NSMutableArray array];
  744. [result addObject:@{ @"anim" : @(_animationOptions) }];
  745. [result addObject:@{ @"type" : NSStringFromASHierarchyChangeType(_changeType) }];
  746. [result addObject:@{ @"indexPaths" : self.indexPaths }];
  747. return result;
  748. }
  749. @end