Se ha denunciado esta presentación.
Utilizamos tu perfil de LinkedIn y tus datos de actividad para personalizar los anuncios y mostrarte publicidad más relevante. Puedes cambiar tus preferencias de publicidad en cualquier momento.

Chainable datasource

3.546 visualizaciones

Publicado el

Une approche qui permet d'alimenter des table views de manière déclarative, d'y combiner des données hétérogènes, et de gérer facilement et surtout de manière fiable les updates, par Amadour Griffais.

Publicado en: Software
  • Sé el primero en comentar

  • Sé el primero en recomendar esto

Chainable datasource

  1. 1. Chainable Data Sources or how I stopped worrying and started abusing table view updates Amadour Griffais CocoaHeads Paris – 8 September 2016
  2. 2. @ * [ : ] ; Disclaimer 🍓
  3. 3. UITableView *tableView;
  4. 4. - (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"fruit-cell" forIndexPath:indexPath]; Fruit* fruit = self.fruits[0]; cell.titleLabel.text = fruit.name; return cell; } 😎
  5. 5. Animations 😍 [tableView beginUpdates]; [self.fruits removeObjectAtIndex:indexPath.row]; [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView endUpdates];
  6. 6. //Controller - (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"fruit-cell" forIndexPath:indexPath]; Fruit* fruit = self.fruits[0]; //Model cell.titleLabel.text = fruit.name; //View return cell; } 🤓
  7. 7. - (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { FruitCell* cell = (FruitCell*)[tableView dequeueReusableCellWithIdentifier:@"fruit-cell" forIndexPath:indexPath]; cell.fruit = fruit; return cell; } 🤔
  8. 8. - (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == FRUIT_SECTION) { UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"fruit-cell" forIndexPath:indexPath]; Fruit* fruit = self.fruits[0]; cell.titleLabel.text = fruit.name; return cell; } else if (indexPath.section == VEGETABLE_SECTION) { UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"veg-cell" forIndexPath:indexPath]; ... 😒
  9. 9. - (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { switch (indexPath.section) { case FRUIT_SECTION: { UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"fruit-cell" forIndexPath:indexPath]; Fruit* fruit = self.fruits[0]; cell.titleLabel.text = fruit.name; return cell; } break; case VEGETABLE_SECTION: { ... 😟
  10. 10. - (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == AD_SECTION && indexPath.row == AD_ROW) { return [tableView dequeueReusableCellWithIdentifier:@"ad-cell" forIndexPath:indexPath]; } indexPath = [self offsetIndexPath:indexPath ifAfter:AD_INDEX_PATH]; switch (indexPath.section) { case FRUIT_SECTION: { UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"fruit-cell" forIndexPath:indexPath]; ... 😨
  11. 11. - (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == AD_SECTION && indexPath.row == AD_ROW && self.isAdLoaded) { return [tableView dequeueReusableCellWithIdentifier:@"ad-cell" forIndexPath:indexPath]; } indexPath = [self offsetIndexPath:indexPath ifAfter:AD_INDEX_PATH]; switch (indexPath.section) { case FRUIT_SECTION: { UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"fruit-cell" forIndexPath:indexPath]; ... 😰
  12. 12. Animations 😱 2016-09-06 14:07:23.167 MyBeautifulTableView[89346:883532] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (9), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).' *** First throw call stack: ( 0 CoreFoundation 0x000000010eba034b __exceptionPreprocess + 171 1 libobjc.A.dylib 0x000000010e60121e objc_exception_throw + 48 2 CoreFoundation 0x000000010eba4442 +[NSException raise:format:arguments:] + 98 3 Foundation 0x0000000109620edd -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 195 4 UIKit 0x000000010c2172f4 -[UITableView _endCellAnimationsWithContext:] + 17558 ...
  13. 13. [tableView reloadData]; 😢
  14. 14. UITableViewDataSource
  15. 15. UITableViewDataSource 🍓 🍆 💵 M C V
  16. 16. UITableViewDataSource 💩 🍓 🍆 💵 M C V
  17. 17. MCV 🍓 🍆 + 💵 < 🍓 🍓 🍆 🍓 💵 🍆
  18. 18. 🍓 🍆 + 💵 < ChainableDataSource
  19. 19. @protocol ChainableDataSource <NSObject> - (NSInteger) numberOfSectionsInDataSource; - (NSInteger) numberOfObjectsInDataSourceSection:(NSInteger)section; - (id) dataSourceObjectAtIndexPath:(NSIndexPath*)indexPath; - (NSString*) nameForDataSourceSectionAtIndex:(NSInteger)section; @property (weak) id<ChainableDataSourceDelegate> dataSourceDelegate; @end
  20. 20. 🍓 🍆 💵 M
  21. 21. 🍓 🍆 💵 M NSArray (ChainableDataSource) FetchedResultsDataSource <#YourChainableDataSource#>
  22. 22. MCV 🍓 🍆 + 💵 < 🍓 🍓 🍆 🍓 💵 🍆
  23. 23. 🍓 CellDataSource
  24. 24. 🍓 CellDataSource @interface CellDataSource : NSObject <ChainableDataSource, UITableViewDataSource> - (NSString*) cellIdentifierForObject:(id)object; - (void) configureCell:(UIView<GenericCell>*)cell withObject:(id)object;
  25. 25. MCV 🍓 🍆 + 💵 < 🍓 🍓 🍆 🍓 💵 🍆
  26. 26. + <
  27. 27. + < <#YourChainableDataSource#>
  28. 28. + < TransformDataSource <#YourChainableDataSource#>
  29. 29. @interface TransformDataSource : NSObject <ChainableDataSource> @property (nonatomic, copy) NSArray<id<ChainableDataSource>>* dataSources; - (NSIndexPath*) sourceIndexPathForIndexPath:(NSIndexPath*)indexPath; - (NSIndexPath*) indexPathForSourceIndexPath:(NSIndexPath*)sourceIndexPath inDataSource:(id<ChainableDataSource>)sourceDataSource; - (NSInteger) sectionIndexForSourceSectionIndex:(NSInteger)sourceSection inDataSource:(id<ChainableDataSource>)sourceDataSource; - (NSIndexPath*) sourceSectionIndexPathForSectionIndex:(NSInteger)section; @end
  30. 30. + < TransformDataSource <#YourChainableDataSource#>
  31. 31. + TransformDataSource <#YourChainableDataSource#> ConcatenatedSectionsDataSource
  32. 32. < TransformDataSource <#YourChainableDataSource#> ConcatenatedSectionsDataSource InsertionDataSource
  33. 33. TransformDataSource <#YourChainableDataSource#> ConcatenatedSectionsDataSource InsertionDataSource 🎉SwitchDataSource FlattenedDataSource FilterDataSource PlaceholderDataSource EmptySectionFilterDataSource <#YourTransformDataSource#>
  34. 34. Updates
  35. 35. MCV 🍓 🍆 + 💵 < 🍓 🍓 🍆 🍓 💵 🍆
  36. 36. 🍓 🍆 + 💵 < 🍓 🍓 🍆 🍓 💵 🍆 ChainableDataSourceDelegate
  37. 37. @protocol ChainableDataSourceDelegate <NSObject> - (void) dataSourceDidReload:(id<ChainableDataSource>)dataSource; - (void) dataSourceWillUpdate:(id<ChainableDataSource>)dataSource; - (void) dataSourceDidUpdate:(id<ChainableDataSource>)dataSource; - (void) dataSource:(id<ChainableDataSource>)dataSource didDeleteSectionsAtIndexes:(NSIndexSet*)sectionIndexes; - (void) dataSource:(id<ChainableDataSource>)dataSource didInsertSectionsAtIndexes:(NSIndexSet*)sectionIndexes; - (void) dataSource:(id<ChainableDataSource>)dataSource didDeleteObjectsAtIndexPaths:(NSArray<NSIndexPath*>*)indexPath; - (void) dataSource:(id<ChainableDataSource>)dataSource didInsertObjectsAtIndexPaths:(NSArray<NSIndexPath*>*)indexPath; - (void) dataSource:(id<ChainableDataSource>)dataSource didUpdateObjectsAtIndexPaths:(NSArray<NSIndexPath*>*)indexPath; @end
  38. 38. A B E D C F A C H D G The truth about updates
  39. 39. A B E D C F A C H D G The truth about updates delete: 1, 4, 5 insert: 2, 4
  40. 40. A B E D C F A C H D G The truth about updates update: 1
  41. 41. [tableView beginUpdates]; [self.fruits removeObjectAtIndex:indexPath.row]; [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView endUpdates]; //OK The truth about updates
  42. 42. [tableView beginUpdates]; [self.fruits removeObjectAtIndex:indexPath.row]; [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView endUpdates]; //Still OK The truth about updates
  43. 43. 🍓 🍆 + 💵 < 🍓 🍓 🍆 🍓 💵 🍆 ChainableDataSourceDelegate
  44. 44. 🍓 🍓 🍆 🍓 💵 🍆 UITableView <ChainableDataSourceDelegate> UICollectionView <ChainableDataSourceDelegate>
  45. 45. 🍓 🍆 + 💵 < 🍓 🍓 🍆 🍓 💵 🍆 ChainableDataSourceDelegate
  46. 46. CellDataSource didUpdateObjectsAtIndexPaths -> configureCell:withObject: everything else -> forward
  47. 47. 🍓 🍆 + 💵 < 🍓 🍓 🍆 🍓 💵 🍆 ChainableDataSourceDelegate
  48. 48. 🍓 🍆 💵 <#YourChainableDataSource#> FetchedResultsDatasource
  49. 49. 🍓 🍆 + 💵 < 🍓 🍓 🍆 🍓 💵 🍆 ChainableDataSourceDelegate
  50. 50. + < basic mapping -> automatic TransformDataSource
  51. 51. TransformDataSource advanced mapping: tweak update set pre and post upstream updates - (void) preRefreshTranslateSourceUpdateCache:(UpdateCache*)sourceUpdateCache fromDataSource:(id<ChainableDataSource>)dataSource toUpdateCache:(UpdateCache*)updateCache; - (void) postRefreshTranslateSourceUpdateCache:(UpdateCache*)sourceUpdateCache fromDataSource:(id<ChainableDataSource>)dataSource toUpdateCache:(UpdateCache*)updateCache;
  52. 52. 🍓 🍓 🍓 🍓 🍓 🍓 🍓 dataSourceDidReload
  53. 53. 🍓 🍓 🍓 🍓 🍓 🍓 🍓 dataSourceDidReload dataSourceDidReload (reloadData - 😥 )
  54. 54. 🍓Δ 🍓 🍓 🍓 🍓 🍓 🍓 DeltaUpdateDataSource dataSourceDidReload
  55. 55. A B E D C F A C H D G
  56. 56. A B E D C F A C H D G - = ?
  57. 57. A B E D C F A C H D G delete: 1, 4, 5 insert: 2, 4 diff algorithm Longest Common Subsequence
  58. 58. 🍓Δ 🍓 🍓 🍓 🍓 🍓 🍓 DeltaUpdateDataSource dataSourceDidReload
  59. 59. 🍓Δ 🍓 🍓 🍓 🍓 🍓 🍓 DeltaUpdateDataSource dataSourceDidReload dataSourceWillUpdate dataSource…∆ dataSourceDidUpdate😁
  60. 60. Updates //TODO: Nothing
  61. 61. 🍓 🍆 + 💵 < 🍓 🍓 🍆 🍓 💵 🍆 CellDataSource
  62. 62. CellDataSource - (NSString*) cellIdentifierForObject:(id)object { return NSStringFromClass([object class]); } - (void) configureCell:(UITableViewCell*)cell withObject:(id)object { if ([cell.reuseIdentifier isEqual:@"Fruit"]) { //... } else if ([cell.reuseIdentifier isEqual:@"Vegetable"]) { //... } // 😢 }
  63. 63. 🍓 🍆 + 💵 < 🍓 🍓 🍆 🍓 💵 🍆 CellDataSource
  64. 64. 🍓 🍆 + 💵 < 🍓 🍓 🍆 🍓 💵 🍆 CellDataSource: alternative approach
  65. 65. 🍓 🍆 + 💵 < 🍓 🍓 🍆 🍓 💵 🍆 TransformDataSource (CellDataSource) forward delegate/data source methods based on index mapping
  66. 66. TransformDataSource (CellDataSource) forward delegate/data source methods based on index mapping (My dirty secret)
  67. 67. - (void) forwardInvocation:(NSInvocation *)anInvocation { if ([self isForwardableDelegateSelector:anInvocation.selector]) { NSMethodSignature* signature = anInvocation.methodSignature; NSArray* components = [NSStringFromSelector(anInvocation.selector)componentsSeparatedByString:@":"]; for (NSInteger argIndex = 0; argIndex < signature.numberOfArguments; argIndex++) { //skip self and cmd if (argIndex < 2) { continue; } //won't work with targetIndexPathForMoveFromRowAtIndexPath, since two indexPath are present const char* argType = [signature getArgumentTypeAtIndex:argIndex]; if (strcmp(argType, @encode(NSIndexPath*)) == 0) { __unsafe_unretained id arg; [anInvocation getArgument:&arg atIndex:argIndex]; if ([arg isKindOfClass:[NSIndexPath class]]) { NSIndexPath* indexPath = arg; NSIndexPath* fullIndexPath = [self sourceIndexPathForIndexPath:indexPath]; id<ChainableDataSource> dataSource = self.dataSources[[fullIndexPath indexAtPosition:0]]; NSIndexPath* dsIndexPath = [NSIndexPath indexPathForRow:[fullIndexPath indexAtPosition:2] inSection: [fullIndexPath indexAtPosition:1]]; [anInvocation setArgument:&dsIndexPath atIndex:argIndex]; if ([dataSource respondsToSelector:anInvocation.selector]) { [anInvocation invokeWithTarget:dataSource]; } return; } } else if (strcmp(argType, @encode(NSInteger)) == 0 && [components[argIndex-2] hasSuffix:@"Section"]) { NSInteger section; [anInvocation getArgument:&section atIndex:argIndex]; NSIndexPath* sourceSectionIndexPath = [self sourceSectionIndexPathForSectionIndex:section]; if (sourceSectionIndexPath) { id<ChainableDataSource> dataSource = self.dataSources[[sourceSectionIndexPath indexAtPosition:0]]; NSInteger sourceSection = [sourceSectionIndexPath indexAtPosition:1]; [anInvocation setArgument:&sourceSection atIndex:argIndex]; if ([dataSource respondsToSelector:anInvocation.selector]) { [anInvocation invokeWithTarget:dataSource]; } } return; } } return; } return [super forwardInvocation:anInvocation]; }
  68. 68. 🍓 🍆 + 💵 < 🍓 🍓 🍆 🍓 💵 🍆
  69. 69. CellDataSource @implementation FruitCellDataSource - (NSString*) cellIdentifierForObject:(id)object { return @"fruit-cell"; } - (void) configureCell:(UITableViewCell*)cell withObject:(id)object { Fruit* fruit = object; cell.titleLabel.text = fruit.name; } - (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { Fruit* fruit = [self.objectsDataSource dataSourceObjectAtIndexPath:indexPath]; //... } @end
  70. 70. 🍓 🍆 + 💵 < 🍓 🍓 🍆 🍓 💵 🍆 It’s alive!
  71. 71. Demo
  72. 72. Open source*
  73. 73. *before this holiday season Open source*
  74. 74. ? amadour

×