ASTableNode.mm 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. //
  2. // ASTableNode.mm
  3. // AsyncDisplayKit
  4. //
  5. // Created by Steven Ramkumar on 11/4/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 "ASTableNode.h"
  13. #import "ASTableViewInternal.h"
  14. #import "ASEnvironmentInternal.h"
  15. #import "ASDisplayNode+Subclasses.h"
  16. #import "ASDisplayNode+FrameworkPrivate.h"
  17. #import "ASInternalHelpers.h"
  18. #import "ASCellNode+Internal.h"
  19. #import "AsyncDisplayKit+Debug.h"
  20. #import "ASTableView+Undeprecated.h"
  21. #pragma mark - _ASTablePendingState
  22. @interface _ASTablePendingState : NSObject
  23. @property (weak, nonatomic) id <ASTableDelegate> delegate;
  24. @property (weak, nonatomic) id <ASTableDataSource> dataSource;
  25. @property (nonatomic, assign) ASLayoutRangeMode rangeMode;
  26. @property (nonatomic, assign) BOOL allowsSelection;
  27. @property (nonatomic, assign) BOOL allowsSelectionDuringEditing;
  28. @property (nonatomic, assign) BOOL allowsMultipleSelection;
  29. @property (nonatomic, assign) BOOL allowsMultipleSelectionDuringEditing;
  30. @property (nonatomic, assign) BOOL inverted;
  31. @end
  32. @implementation _ASTablePendingState
  33. - (instancetype)init
  34. {
  35. self = [super init];
  36. if (self) {
  37. _rangeMode = ASLayoutRangeModeCount;
  38. _allowsSelection = YES;
  39. _allowsSelectionDuringEditing = NO;
  40. _allowsMultipleSelection = NO;
  41. _allowsMultipleSelectionDuringEditing = NO;
  42. _inverted = NO;
  43. }
  44. return self;
  45. }
  46. @end
  47. #pragma mark - ASTableView
  48. @interface ASTableNode ()
  49. {
  50. ASDN::RecursiveMutex _environmentStateLock;
  51. }
  52. @property (nonatomic, strong) _ASTablePendingState *pendingState;
  53. @end
  54. @interface ASTableView ()
  55. - (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass;
  56. @end
  57. @implementation ASTableNode
  58. #pragma mark Lifecycle
  59. - (instancetype)_initWithTableView:(ASTableView *)tableView
  60. {
  61. // Avoid a retain cycle. In this case, the ASTableView is creating us, and strongly retains us.
  62. ASTableView * __weak weakTableView = tableView;
  63. if (self = [super initWithViewBlock:^UIView *{ return weakTableView; }]) {
  64. __unused __weak ASTableView *view = [self view];
  65. return self;
  66. }
  67. return nil;
  68. }
  69. - (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass
  70. {
  71. __weak __typeof__(self) weakSelf = self;
  72. ASDisplayNodeViewBlock tableViewBlock = ^UIView *{
  73. // Variable will be unused if event logging is off.
  74. __unused __typeof__(self) strongSelf = weakSelf;
  75. return [[ASTableView alloc] _initWithFrame:frame style:style dataControllerClass:dataControllerClass eventLog:ASDisplayNodeGetEventLog(strongSelf)];
  76. };
  77. if (self = [super initWithViewBlock:tableViewBlock]) {
  78. return self;
  79. }
  80. return nil;
  81. }
  82. - (instancetype)initWithStyle:(UITableViewStyle)style
  83. {
  84. return [self _initWithFrame:CGRectZero style:style dataControllerClass:nil];
  85. }
  86. - (instancetype)init
  87. {
  88. return [self _initWithFrame:CGRectZero style:UITableViewStylePlain dataControllerClass:nil];
  89. }
  90. #pragma mark ASDisplayNode
  91. - (void)didLoad
  92. {
  93. [super didLoad];
  94. ASTableView *view = self.view;
  95. view.tableNode = self;
  96. if (_pendingState) {
  97. _ASTablePendingState *pendingState = _pendingState;
  98. self.pendingState = nil;
  99. view.asyncDelegate = pendingState.delegate;
  100. view.asyncDataSource = pendingState.dataSource;
  101. view.inverted = pendingState.inverted;
  102. view.allowsSelection = pendingState.allowsSelection;
  103. view.allowsSelectionDuringEditing = pendingState.allowsSelectionDuringEditing;
  104. view.allowsMultipleSelection = pendingState.allowsMultipleSelection;
  105. view.allowsMultipleSelectionDuringEditing = pendingState.allowsMultipleSelectionDuringEditing;
  106. if (pendingState.rangeMode != ASLayoutRangeModeCount) {
  107. [view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode];
  108. }
  109. }
  110. }
  111. - (ASTableView *)view
  112. {
  113. return (ASTableView *)[super view];
  114. }
  115. - (void)clearContents
  116. {
  117. [super clearContents];
  118. [self.rangeController clearContents];
  119. }
  120. - (void)didExitPreloadState
  121. {
  122. [super didExitPreloadState];
  123. [self.rangeController clearPreloadedData];
  124. }
  125. - (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState
  126. {
  127. [super interfaceStateDidChange:newState fromState:oldState];
  128. [ASRangeController layoutDebugOverlayIfNeeded];
  129. }
  130. #if ASRangeControllerLoggingEnabled
  131. - (void)didEnterVisibleState
  132. {
  133. [super didEnterVisibleState];
  134. NSLog(@"%@ - visible: YES", self);
  135. }
  136. - (void)didExitVisibleState
  137. {
  138. [super didExitVisibleState];
  139. NSLog(@"%@ - visible: NO", self);
  140. }
  141. #endif
  142. #pragma mark Setter / Getter
  143. // TODO: Implement this without the view.
  144. - (ASDataController *)dataController
  145. {
  146. return self.view.dataController;
  147. }
  148. // TODO: Implement this without the view.
  149. - (ASRangeController *)rangeController
  150. {
  151. return self.view.rangeController;
  152. }
  153. - (_ASTablePendingState *)pendingState
  154. {
  155. if (!_pendingState && ![self isNodeLoaded]) {
  156. _pendingState = [[_ASTablePendingState alloc] init];
  157. }
  158. ASDisplayNodeAssert(![self isNodeLoaded] || !_pendingState, @"ASTableNode should not have a pendingState once it is loaded");
  159. return _pendingState;
  160. }
  161. - (void)setInverted:(BOOL)inverted
  162. {
  163. self.transform = inverted ? CATransform3DMakeScale(1, -1, 1) : CATransform3DIdentity;
  164. if ([self pendingState]) {
  165. _pendingState.inverted = inverted;
  166. } else {
  167. ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist");
  168. self.view.inverted = inverted;
  169. }
  170. }
  171. - (BOOL)inverted
  172. {
  173. if ([self pendingState]) {
  174. return _pendingState.inverted;
  175. } else {
  176. return self.view.inverted;
  177. }
  178. }
  179. - (void)setDelegate:(id <ASTableDelegate>)delegate
  180. {
  181. if ([self pendingState]) {
  182. _pendingState.delegate = delegate;
  183. } else {
  184. ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist");
  185. // Manually trampoline to the main thread. The view requires this be called on main
  186. // and asserting here isn't an option – it is a common pattern for users to clear
  187. // the delegate/dataSource in dealloc, which may be running on a background thread.
  188. // It is important that we avoid retaining self in this block, so that this method is dealloc-safe.
  189. ASTableView *view = self.view;
  190. ASPerformBlockOnMainThread(^{
  191. view.asyncDelegate = delegate;
  192. });
  193. }
  194. }
  195. - (id <ASTableDelegate>)delegate
  196. {
  197. if ([self pendingState]) {
  198. return _pendingState.delegate;
  199. } else {
  200. return self.view.asyncDelegate;
  201. }
  202. }
  203. - (void)setDataSource:(id <ASTableDataSource>)dataSource
  204. {
  205. if ([self pendingState]) {
  206. _pendingState.dataSource = dataSource;
  207. } else {
  208. ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist");
  209. // Manually trampoline to the main thread. The view requires this be called on main
  210. // and asserting here isn't an option – it is a common pattern for users to clear
  211. // the delegate/dataSource in dealloc, which may be running on a background thread.
  212. // It is important that we avoid retaining self in this block, so that this method is dealloc-safe.
  213. ASTableView *view = self.view;
  214. ASPerformBlockOnMainThread(^{
  215. view.asyncDataSource = dataSource;
  216. });
  217. }
  218. }
  219. - (id <ASTableDataSource>)dataSource
  220. {
  221. if ([self pendingState]) {
  222. return _pendingState.dataSource;
  223. } else {
  224. ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist");
  225. return self.view.asyncDataSource;
  226. }
  227. }
  228. - (void)setAllowsSelection:(BOOL)allowsSelection
  229. {
  230. if ([self pendingState]) {
  231. _pendingState.allowsSelection = allowsSelection;
  232. } else {
  233. ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist");
  234. self.view.allowsSelection = allowsSelection;
  235. }
  236. }
  237. - (BOOL)allowsSelection
  238. {
  239. if ([self pendingState]) {
  240. return _pendingState.allowsSelection;
  241. } else {
  242. return self.view.allowsSelection;
  243. }
  244. }
  245. - (void)setAllowsSelectionDuringEditing:(BOOL)allowsSelectionDuringEditing
  246. {
  247. if ([self pendingState]) {
  248. _pendingState.allowsSelectionDuringEditing = allowsSelectionDuringEditing;
  249. } else {
  250. ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist");
  251. self.view.allowsSelectionDuringEditing = allowsSelectionDuringEditing;
  252. }
  253. }
  254. - (BOOL)allowsSelectionDuringEditing
  255. {
  256. if ([self pendingState]) {
  257. return _pendingState.allowsSelectionDuringEditing;
  258. } else {
  259. return self.view.allowsSelectionDuringEditing;
  260. }
  261. }
  262. - (void)setAllowsMultipleSelection:(BOOL)allowsMultipleSelection
  263. {
  264. if ([self pendingState]) {
  265. _pendingState.allowsMultipleSelection = allowsMultipleSelection;
  266. } else {
  267. ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist");
  268. self.view.allowsMultipleSelection = allowsMultipleSelection;
  269. }
  270. }
  271. - (BOOL)allowsMultipleSelection
  272. {
  273. if ([self pendingState]) {
  274. return _pendingState.allowsMultipleSelection;
  275. } else {
  276. return self.view.allowsMultipleSelection;
  277. }
  278. }
  279. - (void)setAllowsMultipleSelectionDuringEditing:(BOOL)allowsMultipleSelectionDuringEditing
  280. {
  281. if ([self pendingState]) {
  282. _pendingState.allowsMultipleSelectionDuringEditing = allowsMultipleSelectionDuringEditing;
  283. } else {
  284. ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist");
  285. self.view.allowsMultipleSelectionDuringEditing = allowsMultipleSelectionDuringEditing;
  286. }
  287. }
  288. - (BOOL)allowsMultipleSelectionDuringEditing
  289. {
  290. if ([self pendingState]) {
  291. return _pendingState.allowsMultipleSelectionDuringEditing;
  292. } else {
  293. return self.view.allowsMultipleSelectionDuringEditing;
  294. }
  295. }
  296. #pragma mark ASRangeControllerUpdateRangeProtocol
  297. - (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode
  298. {
  299. if ([self pendingState]) {
  300. _pendingState.rangeMode = rangeMode;
  301. } else {
  302. ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist");
  303. [self.rangeController updateCurrentRangeWithMode:rangeMode];
  304. }
  305. }
  306. #pragma mark ASEnvironment
  307. ASEnvironmentCollectionTableSetEnvironmentState(_environmentStateLock)
  308. #pragma mark - Range Tuning
  309. - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)rangeType
  310. {
  311. return [self.rangeController tuningParametersForRangeMode:ASLayoutRangeModeFull rangeType:rangeType];
  312. }
  313. - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType
  314. {
  315. [self.rangeController setTuningParameters:tuningParameters forRangeMode:ASLayoutRangeModeFull rangeType:rangeType];
  316. }
  317. - (ASRangeTuningParameters)tuningParametersForRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType
  318. {
  319. return [self.rangeController tuningParametersForRangeMode:rangeMode rangeType:rangeType];
  320. }
  321. - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeMode:(ASLayoutRangeMode)rangeMode rangeType:(ASLayoutRangeType)rangeType
  322. {
  323. return [self.rangeController setTuningParameters:tuningParameters forRangeMode:rangeMode rangeType:rangeType];
  324. }
  325. #pragma mark - Selection
  326. - (void)selectRowAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition
  327. {
  328. ASDisplayNodeAssertMainThread();
  329. ASTableView *tableView = self.view;
  330. indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES];
  331. if (indexPath != nil) {
  332. [tableView selectRowAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition];
  333. } else {
  334. NSLog(@"Failed to select row at index path %@ because the row never reached the view.", indexPath);
  335. }
  336. }
  337. - (void)deselectRowAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated
  338. {
  339. ASDisplayNodeAssertMainThread();
  340. ASTableView *tableView = self.view;
  341. indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES];
  342. if (indexPath != nil) {
  343. [tableView deselectRowAtIndexPath:indexPath animated:animated];
  344. } else {
  345. NSLog(@"Failed to deselect row at index path %@ because the row never reached the view.", indexPath);
  346. }
  347. }
  348. - (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated
  349. {
  350. ASDisplayNodeAssertMainThread();
  351. ASTableView *tableView = self.view;
  352. indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES];
  353. if (indexPath != nil) {
  354. [tableView scrollToRowAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated];
  355. } else {
  356. NSLog(@"Failed to scroll to row at index path %@ because the row never reached the view.", indexPath);
  357. }
  358. }
  359. #pragma mark - Querying Data
  360. - (void)reloadDataInitiallyIfNeeded
  361. {
  362. ASDisplayNodeAssertMainThread();
  363. if (!self.dataController.initialReloadDataHasBeenCalled) {
  364. // Note: Just calling reloadData isn't enough here – we need to
  365. // ensure that _nodesConstrainedWidth is updated first.
  366. [self.view layoutIfNeeded];
  367. }
  368. }
  369. - (NSInteger)numberOfRowsInSection:(NSInteger)section
  370. {
  371. ASDisplayNodeAssertMainThread();
  372. [self reloadDataInitiallyIfNeeded];
  373. return [self.dataController numberOfRowsInSection:section];
  374. }
  375. - (NSInteger)numberOfSections
  376. {
  377. ASDisplayNodeAssertMainThread();
  378. [self reloadDataInitiallyIfNeeded];
  379. return [self.dataController numberOfSections];
  380. }
  381. - (NSArray<__kindof ASCellNode *> *)visibleNodes
  382. {
  383. ASDisplayNodeAssertMainThread();
  384. return self.isNodeLoaded ? [self.view visibleNodes] : @[];
  385. }
  386. - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode
  387. {
  388. return [self.dataController indexPathForNode:cellNode];
  389. }
  390. - (ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath
  391. {
  392. [self reloadDataInitiallyIfNeeded];
  393. return [self.dataController nodeAtIndexPath:indexPath];
  394. }
  395. - (CGRect)rectForRowAtIndexPath:(NSIndexPath *)indexPath
  396. {
  397. ASDisplayNodeAssertMainThread();
  398. ASTableView *tableView = self.view;
  399. indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES];
  400. return [tableView rectForRowAtIndexPath:indexPath];
  401. }
  402. - (nullable __kindof UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath
  403. {
  404. ASDisplayNodeAssertMainThread();
  405. ASTableView *tableView = self.view;
  406. indexPath = [tableView convertIndexPathFromTableNode:indexPath waitingIfNeeded:YES];
  407. if (indexPath == nil) {
  408. return nil;
  409. }
  410. return [tableView cellForRowAtIndexPath:indexPath];
  411. }
  412. - (nullable NSIndexPath *)indexPathForSelectedRow
  413. {
  414. ASDisplayNodeAssertMainThread();
  415. ASTableView *tableView = self.view;
  416. NSIndexPath *indexPath = tableView.indexPathForSelectedRow;
  417. if (indexPath != nil) {
  418. return [tableView convertIndexPathToTableNode:indexPath];
  419. }
  420. return indexPath;
  421. }
  422. - (NSArray<NSIndexPath *> *)indexPathsForSelectedRows
  423. {
  424. ASDisplayNodeAssertMainThread();
  425. ASTableView *tableView = self.view;
  426. return [tableView convertIndexPathsToTableNode:tableView.indexPathsForSelectedRows];
  427. }
  428. - (nullable NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point
  429. {
  430. ASDisplayNodeAssertMainThread();
  431. ASTableView *tableView = self.view;
  432. NSIndexPath *indexPath = [tableView indexPathForRowAtPoint:point];
  433. if (indexPath != nil) {
  434. return [tableView convertIndexPathToTableNode:indexPath];
  435. }
  436. return indexPath;
  437. }
  438. - (nullable NSArray<NSIndexPath *> *)indexPathsForRowsInRect:(CGRect)rect
  439. {
  440. ASDisplayNodeAssertMainThread();
  441. ASTableView *tableView = self.view;
  442. return [tableView convertIndexPathsToTableNode:[tableView indexPathsForRowsInRect:rect]];
  443. }
  444. - (NSArray<NSIndexPath *> *)indexPathsForVisibleRows
  445. {
  446. ASDisplayNodeAssertMainThread();
  447. NSMutableArray *indexPathsArray = [NSMutableArray new];
  448. for (ASCellNode *cell in [self visibleNodes]) {
  449. NSIndexPath *indexPath = [self indexPathForNode:cell];
  450. if (indexPath) {
  451. [indexPathsArray addObject:indexPath];
  452. }
  453. }
  454. return indexPathsArray;
  455. }
  456. #pragma mark - Editing
  457. - (void)reloadDataWithCompletion:(void (^)())completion
  458. {
  459. [self.view reloadDataWithCompletion:completion];
  460. }
  461. - (void)reloadData
  462. {
  463. [self reloadDataWithCompletion:nil];
  464. }
  465. - (void)relayoutItems
  466. {
  467. [self.view relayoutItems];
  468. }
  469. - (void)performBatchAnimated:(BOOL)animated updates:(void (^)())updates completion:(void (^)(BOOL))completion
  470. {
  471. [self.view beginUpdates];
  472. if (updates) {
  473. updates();
  474. }
  475. [self.view endUpdatesAnimated:animated completion:completion];
  476. }
  477. - (void)performBatchUpdates:(void (^)())updates completion:(void (^)(BOOL))completion
  478. {
  479. [self performBatchAnimated:YES updates:updates completion:completion];
  480. }
  481. - (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
  482. {
  483. [self.view insertSections:sections withRowAnimation:animation];
  484. }
  485. - (void)deleteSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
  486. {
  487. [self.view deleteSections:sections withRowAnimation:animation];
  488. }
  489. - (void)reloadSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation
  490. {
  491. [self.view reloadSections:sections withRowAnimation:animation];
  492. }
  493. - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection
  494. {
  495. [self.view moveSection:section toSection:newSection];
  496. }
  497. - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
  498. {
  499. [self.view insertRowsAtIndexPaths:indexPaths withRowAnimation:animation];
  500. }
  501. - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
  502. {
  503. [self.view deleteRowsAtIndexPaths:indexPaths withRowAnimation:animation];
  504. }
  505. - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
  506. {
  507. [self.view reloadRowsAtIndexPaths:indexPaths withRowAnimation:animation];
  508. }
  509. - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
  510. {
  511. [self.view moveRowAtIndexPath:indexPath toIndexPath:newIndexPath];
  512. }
  513. - (void)waitUntilAllUpdatesAreCommitted
  514. {
  515. [self.view waitUntilAllUpdatesAreCommitted];
  516. }
  517. #pragma mark - Debugging (Private)
  518. - (NSMutableArray<NSDictionary *> *)propertiesForDebugDescription
  519. {
  520. NSMutableArray<NSDictionary *> *result = [super propertiesForDebugDescription];
  521. [result addObject:@{ @"dataSource" : ASObjectDescriptionMakeTiny(self.dataSource) }];
  522. [result addObject:@{ @"delegate" : ASObjectDescriptionMakeTiny(self.delegate) }];
  523. return result;
  524. }
  525. @end