ASCollectionNode.mm 18 KB

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