Slide 1

Slide 1 text

Pushing the limits of protocol-oriented programming Jesse Squires jessesquires.com • @jesse_squires

Slide 2

Slide 2 text

The Swift way How do we write swifty code? Standard library: "Protocols and value types all the way down."

Slide 3

Slide 3 text

Writing "Swifty" code means writing protocol-oriented code

Slide 4

Slide 4 text

What is protocol-oriented programming?

Slide 5

Slide 5 text

SOLID Design Principles

Slide 6

Slide 6 text

SOLID Interface Segregation

Slide 7

Slide 7 text

"Protocol-oriented" Interface Segregation

Slide 8

Slide 8 text

Wat? What do we mean by "interface segregation"?

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

Why? “Why do we want to use protocols?” — Greg Heo

Slide 11

Slide 11 text

Protocols help us write code that is: 1.modular 2.dynamic 3.testable

Slide 12

Slide 12 text

What if everything were a protocol? (well, almost) Let's find out!

Slide 13

Slide 13 text

Experiment: Let's build protocol-oriented data sources — UITableViewDataSource — UICollectionViewDataSource Table views and collection views are fundamental components of most apps.

Slide 14

Slide 14 text

Goals 1.protocol-based 2.type-safe / generic 3.unify UITableView and UICollectionView 4.remove UIKit boilerplate 5.avoid NSObject and NSObjectProtocol ("pure" Swift)

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Section Protocol protocol SectionInfoProtocol { associatedtype Item var items: [Item] { get set } var headerTitle: String? { get } var footerTitle: String? { get } }

Slide 18

Slide 18 text

Section Type struct Section: SectionInfoProtocol { var items: [Item] let headerTitle: String? let footerTitle: String? }

Slide 19

Slide 19 text

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? }

Slide 20

Slide 20 text

DataSource Type struct DataSource: DataSourceProtocol { var sections: [S] // MARK: DataSourceProtocol func numberOfSections() -> Int { return sections.count } // other protocol methods... }

Slide 21

Slide 21 text

Responsibilities ✅ Structured data 2. Create and configure cells 3. Conform to UITableViewDataSource 4. Conform to UICollectionViewDataSource

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

Unify tables + collections // UITableView // UICollectionView protocol CellParentViewProtocol { associatedtype CellType: UIView func dequeueReusableCellFor(identifier: String, indexPath: IndexPath) -> CellType }

Slide 24

Slide 24 text

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) } }

Slide 25

Slide 25 text

Unify cells // UITableViewCell // UICollectionViewCell protocol ReusableViewProtocol { associatedtype ParentView: UIView, CellParentViewProtocol var reuseIdentifier: String? { get } func prepareForReuse() }

Slide 26

Slide 26 text

Unify cells extension UICollectionViewCell: ReusableViewProtocol { typealias ParentView = UICollectionView } extension UITableViewCell: ReusableViewProtocol { typealias ParentView = UITableView } // already implemented in UIKit // // var reuseIdentifier: String? { get } // func prepareForReuse()

Slide 27

Slide 27 text

Sharing a common interface CellParentViewProtocol » UITableView and UICollectionView ReusableViewProtocol » UITableViewCell and UICollectionViewCell

Slide 28

Slide 28 text

Create + configure cells ✅ A common interface 2. A unified way to create and configure cells

Slide 29

Slide 29 text

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 }

Slide 30

Slide 30 text

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) } }

Slide 31

Slide 31 text

Create + configure cells Type struct ViewConfig: 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) } }

Slide 32

Slide 32 text

Responsibilities ✅ Structured data ✅ Create and configure cells 3. Conform to UITableViewDataSource 4. Conform to UICollectionViewDataSource

Slide 33

Slide 33 text

Data source protocols class BridgedDataSource: NSObject, UICollectionViewDataSource, UITableViewDataSource { // Init with closures for each data source method // Implement UICollectionViewDataSource // Implement UITableViewDataSource }

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Responsibilities ✅ Structured data ✅ Create and configure cells ✅ UITableViewDataSource ✅ UICollectionViewDataSource

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Connecting the pieces class DataSourceProvider where D.Item == C.Item { var dataSource: D let cellConfig: C private var bridgedDataSource: BridgedDataSource? init(dataSource: D, cellConfig: C) }

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

How does that work? // Collections provider.collectionViewDataSource // Tables provider.tableViewDataSource

Slide 40

Slide 40 text

Generating specific data sources // class DataSourceProvider // 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 { // ... } }

Slide 41

Slide 41 text

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) } }

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

Summary Protocols are much more powerful in Swift than in Objective-C. !

Slide 44

Slide 44 text

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!

Slide 45

Slide 45 text

Protocols Restrict access // class DataSourceProvider var tableViewDataSource: UITableViewDataSource Returns BridgedDataSource but clients don't know! (It also conforms to UICollectionViewDataSource)

Slide 46

Slide 46 text

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.

Slide 47

Slide 47 text

Protocols Modular protocol SectionInfoProtocol { } protocol DataSourceProtocol { } protocol ReusableViewConfigProtocol { } Anything can be a data source Anything can configure cells

Slide 48

Slide 48 text

Protocols Testable Easy to "mock" or fake a protocol in a unit test. Easy to verify that a protocol method was called.

Slide 49

Slide 49 text

Protocols + Extensions expand our design space

Slide 50

Slide 50 text

Thanks! Me: @jesse_squires jessesquires.com Swift Weekly Brief: @swiftlybrief swiftweekly.github.io