+ All Categories
Home > Software > Chainable datasource

Chainable datasource

Date post: 06-Jan-2017
Category:
Upload: cocoaheads-france
View: 1,798 times
Download: 0 times
Share this document with a friend
76
Chainable Data Sources or how I stopped worrying and started abusing table view updates Amadour Griffais CocoaHeads Paris – 8 September 2016
Transcript
Page 1: Chainable datasource

Chainable Data Sources

or how I stopped worrying and started abusing table view updates

Amadour Griffais

CocoaHeads Paris – 8 September 2016

Page 2: Chainable datasource

@ * [ : ] ;

Disclaimer

🍓

Page 3: Chainable datasource

UITableView *tableView;

Page 4: Chainable datasource

- (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; }

😎

Page 5: Chainable datasource

Animations 😍[tableView beginUpdates]; [self.fruits removeObjectAtIndex:indexPath.row]; [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView endUpdates];

Page 6: Chainable datasource

//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; }

🤓

Page 7: Chainable datasource

- (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { FruitCell* cell = (FruitCell*)[tableView dequeueReusableCellWithIdentifier:@"fruit-cell" forIndexPath:indexPath]; cell.fruit = fruit; return cell; }

🤔

Page 8: Chainable datasource

- (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]; ...

😒

Page 9: Chainable datasource

- (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: { ...

😟

Page 10: Chainable datasource

- (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]; ...

😨

Page 11: Chainable datasource

- (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]; ...

😰

Page 12: Chainable datasource

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 ...

Page 13: Chainable datasource

[tableView reloadData];

😢

Page 14: Chainable datasource
Page 15: Chainable datasource

UITableViewDataSource

Page 16: Chainable datasource

UITableViewDataSource

🍓 🍆

💵

♻M

CV

Page 17: Chainable datasource

UITableViewDataSource

💩

🍓 🍆

💵

♻M

CV

Page 18: Chainable datasource

MCV

🍓

🍆

+

💵

<

🍓

🍓

🍆

🍓

💵

🍆

Page 19: Chainable datasource

🍓

🍆

+

💵

<

ChainableDataSource

Page 20: Chainable datasource

@protocol ChainableDataSource <NSObject>

- (NSInteger) numberOfSectionsInDataSource; - (NSInteger) numberOfObjectsInDataSourceSection:(NSInteger)section; - (id) dataSourceObjectAtIndexPath:(NSIndexPath*)indexPath; - (NSString*) nameForDataSourceSectionAtIndex:(NSInteger)section;

@property (weak) id<ChainableDataSourceDelegate> dataSourceDelegate;

@end

Page 21: Chainable datasource

🍓

🍆

💵

M

Page 22: Chainable datasource

🍓

🍆

💵

M

NSArray (ChainableDataSource)

FetchedResultsDataSource

<#YourChainableDataSource#>

Page 23: Chainable datasource

MCV

🍓

🍆

+

💵

<

🍓

🍓

🍆

🍓

💵

🍆

Page 24: Chainable datasource
Page 25: Chainable datasource

🍓

CellDataSource

Page 26: Chainable datasource

🍓

CellDataSource

@interface CellDataSource : NSObject <ChainableDataSource, UITableViewDataSource> - (NSString*) cellIdentifierForObject:(id)object; - (void) configureCell:(UIView<GenericCell>*)cell withObject:(id)object;

Page 27: Chainable datasource

MCV

🍓

🍆

+

💵

<

🍓

🍓

🍆

🍓

💵

🍆

Page 28: Chainable datasource

+

<

Page 29: Chainable datasource

+

<

<#YourChainableDataSource#>

Page 30: Chainable datasource

+

<

TransformDataSource<#YourChainableDataSource#>

Page 31: Chainable datasource

@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

Page 32: Chainable datasource

+

<

TransformDataSource<#YourChainableDataSource#>

Page 33: Chainable datasource

+

TransformDataSource<#YourChainableDataSource#>

ConcatenatedSectionsDataSource

Page 34: Chainable datasource

<

TransformDataSource<#YourChainableDataSource#>

ConcatenatedSectionsDataSourceInsertionDataSource

Page 35: Chainable datasource

TransformDataSource<#YourChainableDataSource#>

ConcatenatedSectionsDataSourceInsertionDataSource

🎉SwitchDataSourceFlattenedDataSource

FilterDataSourcePlaceholderDataSource

EmptySectionFilterDataSource<#YourTransformDataSource#>

Page 36: Chainable datasource

Updates

Page 37: Chainable datasource

MCV

🍓

🍆

+

💵

<

🍓

🍓

🍆

🍓

💵

🍆

Page 38: Chainable datasource

🍓

🍆

+

💵

<

🍓

🍓

🍆

🍓

💵

🍆

ChainableDataSourceDelegate

Page 39: Chainable datasource

@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

Page 40: Chainable datasource

A

B

E

D

C

F

A

C

H

D

G

The truth about updates

Page 41: Chainable datasource

A

B

E

D

C

F

A

C

H

D

G

The truth about updatesdelete: 1, 4, 5 insert: 2, 4

Page 42: Chainable datasource

A

B

E

D

C

F

A

C

H

D

G

The truth about updatesupdate: 1

Page 43: Chainable datasource

[tableView beginUpdates]; [self.fruits removeObjectAtIndex:indexPath.row]; [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; [tableView endUpdates]; //OK

The truth about updates

Page 44: Chainable datasource

[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

Page 45: Chainable datasource

🍓

🍆

+

💵

<

🍓

🍓

🍆

🍓

💵

🍆

ChainableDataSourceDelegate

Page 46: Chainable datasource

🍓

🍓

🍆

🍓

💵

🍆

UITableView <ChainableDataSourceDelegate>UICollectionView <ChainableDataSourceDelegate>

Page 47: Chainable datasource

🍓

🍆

+

💵

<

🍓

🍓

🍆

🍓

💵

🍆

ChainableDataSourceDelegate

Page 48: Chainable datasource

CellDataSource

didUpdateObjectsAtIndexPaths -> configureCell:withObject: everything else -> forward

Page 49: Chainable datasource

🍓

🍆

+

💵

<

🍓

🍓

🍆

🍓

💵

🍆

ChainableDataSourceDelegate

Page 50: Chainable datasource

🍓

🍆

💵

<#YourChainableDataSource#>

FetchedResultsDatasource

Page 51: Chainable datasource

🍓

🍆

+

💵

<

🍓

🍓

🍆

🍓

💵

🍆

ChainableDataSourceDelegate

Page 52: Chainable datasource

+

<

basic mapping -> automatic

TransformDataSource

Page 53: Chainable datasource

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;

Page 54: Chainable datasource

🍓

🍓

🍓

🍓

🍓

🍓

🍓

dataSourceDidReload

Page 55: Chainable datasource

🍓

🍓

🍓

🍓

🍓

🍓

🍓

dataSourceDidReloaddataSourceDidReload (reloadData - 😥 )

Page 56: Chainable datasource

🍓Δ

🍓

🍓

🍓

🍓

🍓

🍓

DeltaUpdateDataSource

dataSourceDidReload

Page 57: Chainable datasource

A

B

E

D

C

F

A

C

H

D

G

Page 58: Chainable datasource

A

B

E

D

C

F

A

C

H

D

G- = ?

Page 59: Chainable datasource

A

B

E

D

C

F

A

C

H

D

G

delete: 1, 4, 5 insert: 2, 4

diff algorithm Longest Common Subsequence

Page 60: Chainable datasource

🍓Δ

🍓

🍓

🍓

🍓

🍓

🍓

DeltaUpdateDataSource

dataSourceDidReload

Page 61: Chainable datasource

🍓Δ

🍓

🍓

🍓

🍓

🍓

🍓

DeltaUpdateDataSource

dataSourceDidReload

dataSourceWillUpdate dataSource…∆ dataSourceDidUpdate😁

Page 62: Chainable datasource

Updates//TODO: Nothing

Page 63: Chainable datasource

🍓

🍆

+

💵

<

🍓

🍓

🍆

🍓

💵

🍆

CellDataSource

Page 64: Chainable datasource

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"]) { //... } //😢 }

Page 65: Chainable datasource

🍓

🍆

+

💵

<

🍓

🍓

🍆

🍓

💵

🍆

CellDataSource

Page 66: Chainable datasource

🍓

🍆

+

💵

<

🍓

🍓

🍆

🍓

💵

🍆

CellDataSource: alternative approach

Page 67: Chainable datasource

🍓

🍆

+

💵

<

🍓

🍓

🍆

🍓

💵

🍆

TransformDataSource (CellDataSource) forward delegate/data source methods

based on index mapping

Page 68: Chainable datasource

TransformDataSource (CellDataSource) forward delegate/data source methods

based on index mapping

(My dirty secret)

Page 69: Chainable datasource

- (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]; }

Page 70: Chainable datasource

🍓

🍆

+

💵

<

🍓

🍓

🍆

🍓

💵

🍆

Page 71: Chainable datasource

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

Page 72: Chainable datasource

🍓

🍆

+

💵

<

🍓

🍓

🍆

🍓

💵

🍆

It’s alive!

Page 73: Chainable datasource

Demo

Page 74: Chainable datasource

Open source*

Page 75: Chainable datasource

*before this holiday season

Open source*

Page 76: Chainable datasource

?

amadour


Recommended