Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Pushing the limits of protocol-oriented program...

Pushing the limits of protocol-oriented programming

All programming languages have their own "personality" and encourage certain paradigms. In Swift, this is expressed through protocols and a focus on value types. As a community, we convey this by describing code as being "swifty" or not. In this talk, we'll examine how we can push the limits of protocol-oriented programming (and thus, the limits of the Swift type-checker) by building a generic, protocol-oriented ("swifty") data source framework.

Video:
https://www.skilled.io/u/swiftsummit/pushing-the-limits-of-protocol-oriented-programming

GItHub project:
https://github.com/jessesquires/JSQDataSourcesKit

Event:
https://swiftsummit.com

Avatar for Jesse Squires

Jesse Squires

November 08, 2016
Tweet

More Decks by Jesse Squires

Other Decks in Programming

Transcript

  1. The Swift way How do we write swifty code? Standard

    library: "Protocols and value types all the way down."
  2. Protocols The "I" in SOLID: No client should depend on

    (or know about) methods it does not use. » Small, separated interfaces == focused API » Restrict access » Unify disjoint types » Hide concrete types
  3. Experiment: Let's build protocol-oriented data sources — UITableViewDataSource — UICollectionViewDataSource

    Table views and collection views are fundamental components of most apps.
  4. Goals 1.protocol-based 2.type-safe / generic 3.unify UITableView and UICollectionView 4.remove

    UIKit boilerplate 5.avoid NSObject and NSObjectProtocol ("pure" Swift)
  5. Responsibilities Display data in a list or grid. What do

    we need? 1.Structured data (sections with items/rows) 2.Create and configure cells 3.Conform to UITableViewDataSource 4.Conform to UICollectionViewDataSource
  6. Section Protocol protocol SectionInfoProtocol { associatedtype Item var items: [Item]

    { get set } var headerTitle: String? { get } var footerTitle: String? { get } }
  7. DataSource Protocol protocol DataSourceProtocol { associatedtype Item func numberOfSections() ->

    Int func numberOfItems(inSection section: Int) -> Int func item(atRow row: Int, inSection section: Int) -> Item? func headerTitle(inSection section: Int) -> String? func footerTitle(inSection section: Int) -> String? }
  8. DataSource Type struct DataSource<S: SectionInfoProtocol>: DataSourceProtocol { var sections: [S]

    // MARK: DataSourceProtocol func numberOfSections() -> Int { return sections.count } // other protocol methods... }
  9. Responsibilities ✅ Structured data 2. Create and configure cells 3.

    Conform to UITableViewDataSource 4. Conform to UICollectionViewDataSource
  10. Create + configure cells 1) We need a common interface

    (protocol) for: — Tables & collections — Table cells — Collection cells 2) We need a unified way to create + configure cells
  11. Unify tables + collections // UITableView // UICollectionView protocol CellParentViewProtocol

    { associatedtype CellType: UIView func dequeueReusableCellFor(identifier: String, indexPath: IndexPath) -> CellType }
  12. Unify tables + collections // conform collection view // (table

    view is similar) extension UICollectionView: CellParentViewProtocol { typealias CellType = UICollectionViewCell func dequeueReusableCellFor(identifier: String, indexPath: IndexPath) -> CellType { return dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) } }
  13. Unify cells // UITableViewCell // UICollectionViewCell protocol ReusableViewProtocol { associatedtype

    ParentView: UIView, CellParentViewProtocol var reuseIdentifier: String? { get } func prepareForReuse() }
  14. Unify cells extension UICollectionViewCell: ReusableViewProtocol { typealias ParentView = UICollectionView

    } extension UITableViewCell: ReusableViewProtocol { typealias ParentView = UITableView } // already implemented in UIKit // // var reuseIdentifier: String? { get } // func prepareForReuse()
  15. Create + configure cells ✅ A common interface 2. A

    unified way to create and configure cells
  16. Configure cells Protocol // configure a cell with a model

    protocol ReusableViewConfigProtocol { associatedtype Item associatedtype View: ReusableViewProtocol func reuseIdentiferFor(item: Item?, indexPath: IndexPath) -> String func configure(view: View, item: Item?, parentView: View.ParentView, indexPath: IndexPath) -> View }
  17. Create cells extension // for table view, similar for collection

    view extension ReusableViewConfigProtocol where View: UITableViewCell { func tableCellFor(item: Item, tableView: UITableView, indexPath: IndexPath) -> View { let cellId = self.reuseIdentiferFor(item: item, indexPath: indexPath) // CellParentViewProtocol let cell = tableView.dequeueReusableCellFor(identifier: cellId, indexPath: indexPath) as! View return self.configure(view: cell, item: item, parentView: tableView, indexPath: indexPath) } }
  18. Create + configure cells Type struct ViewConfig<Item, Cell: ReusableViewProtocol>: ReusableViewConfigProtocol

    { let reuseId: String let configClosure: (Cell, Item, Cell.ParentView, IndexPath) -> Cell // ReusableViewConfigProtocol func reuseIdentiferFor(item: Item?, indexPath: IndexPath) -> String { return reuseId } func configure(view: View, item: Item?, parentView: View.ParentView, indexPath: IndexPath) -> View { return configClosure(view, item, parentView, indexPath) } }
  19. Responsibilities ✅ Structured data ✅ Create and configure cells 3.

    Conform to UITableViewDataSource 4. Conform to UICollectionViewDataSource
  20. Data source protocols class BridgedDataSource: NSObject, UICollectionViewDataSource, UITableViewDataSource { //

    Init with closures for each data source method // Implement UICollectionViewDataSource // Implement UITableViewDataSource }
  21. Data source protocols Example class BridgedDataSource: NSObject { let numberOfSections:

    () -> Int // other closure properties... } extension BridgedDataSource: UICollectionViewDataSource { func numberOfSections(in collectionView: UICollectionView) -> Int { return self.numberOfSections() } // other data source methods... }
  22. Responsibilities ✅ Structured data ✅ Create and configure cells ✅

    UITableViewDataSource ✅ UICollectionViewDataSource
  23. Everything we need protocol SectionInfoProtocol { } // sections of

    items protocol DataSourceProtocol { } // full data source protocol CellParentViewProtocol { } // tables + collections protocol ReusableViewProtocol { } // cells protocol ReusableViewConfigProtocol { } // configure cells class BridgedDataSource { } // UIKit data sources
  24. Connecting the pieces class DataSourceProvider<D: DataSourceProtocol, C: ReusableViewConfigProtocol> where D.Item

    == C.Item { var dataSource: D let cellConfig: C private var bridgedDataSource: BridgedDataSource? init(dataSource: D, cellConfig: C) }
  25. Results let data = DataSource(sections: /* sections of models */)

    let config = ViewConfig(reuseId: "cellId") { (cell, model, view, indexPath) -> MyCell in // configure cell with model return cell } let provider = DataSourceProvider(dataSource: data, cellConfig: config) // connect to collection collectionView.dataSource = provider.collectionViewDataSource // connect to table tableView.dataSource = provider.tableViewDataSource
  26. Generating specific data sources // class DataSourceProvider<D,C> // private var

    bridgedDataSource: BridgedDataSource? extension DataSourceProvider where C.View: UITableViewCell { public var tableViewDataSource: UITableViewDataSource { // create and return new BridgedDataSource // using self.dataSource and self.cellConfig } private func createTableViewDataSource() -> BridgedDataSource { // ... } }
  27. Generating specific data sources // extension DataSourceProvider where C.View: UITableViewCell

    func createTableViewDataSource() -> BridgedDataSource { let source = BridgedDataSource() source.numberOfSections = { () -> Int in return self.dataSource.numberOfSections() } source.numberOfItemsInSection = { (section) -> Int in return self.dataSource.numberOfItems(inSection: section) } source.tableCellForRowAtIndexPath = { (tableView, indexPath) -> UITableViewCell in let item = self.dataSource.item(at: indexPath) // extension method on ReusableViewConfigProtocol return self.cellConfig.tableCellFor(item: item, tableView: tableView, indexPath: indexPath) } }
  28. Results — One more time let data = DataSource(sections: /*

    sections of models */) let config = ViewConfig(reuseId: "cellId") { (cell, model, view, indexPath) -> MyCell in // configure cell with model return cell } let provider = DataSourceProvider(dataSource: data, cellConfig: config) // connect to collection collectionView.dataSource = provider.collectionViewDataSource // connect to table tableView.dataSource = provider.tableViewDataSource
  29. Protocol Extensions Dynamic interface segregation extension ReusableViewConfigProtocol where View: UITableViewCell

    { func tableCellFor(item: Item, tableView: UITableView, indexPath: IndexPath) -> View } extension DataSourceProvider where C.View: UITableViewCell { var tableViewDataSource: UITableViewDataSource } You cannot access tableViewDataSource if you are creating UICollectionViewCells!
  30. Protocols Restrict access // class DataSourceProvider<D,C> var tableViewDataSource: UITableViewDataSource Returns

    BridgedDataSource but clients don't know! (It also conforms to UICollectionViewDataSource)
  31. Protocols Unify disjoint types Hide types protocol CellParentViewProtocol { }

    protocol ReusableViewProtocol { } We can treat tables, collections and their cells the same way — speaking to the same interface.
  32. Protocols Modular protocol SectionInfoProtocol { } protocol DataSourceProtocol { }

    protocol ReusableViewConfigProtocol { } Anything can be a data source Anything can configure cells
  33. Protocols Testable Easy to "mock" or fake a protocol in

    a unit test. Easy to verify that a protocol method was called.