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

Protocol Obsessed Programming (Pragma Conference 2016)

Protocol Obsessed Programming (Pragma Conference 2016)

Presented at Pragma Conference 2016.

Protocols are awesome: They allow us to decouple our software better. But do we use them enough? Time for an experiment: Let’s use protocols to excess to find out where they improve, and where they diminish our code.

Allergy warning: This talk may contain traces of Swift.

Tammo Freese

October 13, 2016
Tweet

More Decks by Tammo Freese

Other Decks in Programming

Transcript

  1. Protocols •In Objective-C: only a few protocols •Foundation (iOS 10):

    356 classes, 26 protocols •In Swift: way more protocols •Int in Foundation (Swift 3): >=13 protocols
  2. Should We Use More Protocols? Without Protocols Protocol Obsessed! Current

    protocol density time “correct” density ? ? ?
  3. Example • Model for collection view content • Adapter to

    the data source • Updates between two models • (Perform updates on collection view) DataSourceAdapter CollectionUpdates UICollectionView CollectionModel
  4. CollectionModel public struct CollectionModel { public func numberOfItems(in section: Int)

    -> Int public func cellIdentifier(at indexPath: IndexPath) -> String public func cellModel(at indexPath: IndexPath) -> Any mutating public func addCellWith(identifier: String, model: Any) }
  5. DataSourceAdapter • Adapts a CollectionModel to the UICollectionViewDataSource protocol •

    Configuration of cells by overriding a method CollectionUpdates UICollectionView CollectionModel DataSourceAdapter
  6. DataSourceAdapter open class CollectionDataSourceAdapter { public var collectionModel: CollectionModel public

    let dataSource: UICollectionViewDataSource open func configure(cell: UICollectionViewCell, at indexPath: IndexPath, with model: Any) }
  7. Usage (1) class DataSourceAdapter: CollectionDataSourceAdapter { override func configure(cell: UICollectionViewCell,

    at indexPath: IndexPath, with model: Any) { guard let cell = cell as? Cell, let model = model as? String else { return } cell.text = model } }
  8. Usage (2) class ViewController: UICollectionViewController { let adapter: CollectionDataSourceAdapter =

    DataSourceAdapter() override func viewDidLoad() { super.viewDidLoad() var model = CollectionModel() model.addCellWith(identifier: "Cell", model: "A") adapter.collectionModel = model collectionView?.dataSource = adapter.dataSource } … }
  9. CollectionUpdates • Represents the updates between two models • Creation

    via class extension to CollectionModel • Perform updates on UICollectionView via instance method DataSourceAdapter CollectionUpdates UICollectionView CollectionModel
  10. CollectionUpdates public struct CollectionUpdates { let indexPathsForInsertedItems: [IndexPath] let indexPathsForDeletedItems:

    [IndexPath] let indexPathsForReloadedItems: [IndexPath] let indexPathsForMovedItems: [IndexPath: IndexPath] func performOn(_ collectionView: UICollectionView) } public extension CollectionModel { public func updates(to: CollectionModel) -> CollectionUpdates }
  11. Usage (3) var toModel = … collectionView.performBatchUpdates({ let fromModel =

    self.adapter.collectionModel let updates = fromModel.updates(to: toModel) self.adapter.collectionModel = toModel updates.performOn(collectionView) }, completion: nil)
  12. CollectionModel • Model for collection view content • Added: builder

    protocol • Added: function for creation DataSourceAdapter CollectionUpdates UICollectionView CollectionModel
  13. CollectionModel public protocol CollectionModel { func numberOfItems(in section: Int) ->

    Int func cellIdentifier(at indexPath: IndexPath) -> String func cellModel(at indexPath: IndexPath) -> Any } public protocol CollectionModelBuilder: class { func addCellWith(identifier: String, model: Any) } public func collectionModel(builder: (CollectionModelBuilder) -> Void) -> CollectionModel
  14. DataSourceAdapter • Adapts a CollectionModel to the UICollectionViewDataSource protocol •

    Configuration of cells by overriding a method CollectionUpdates UICollectionView CollectionModel DataSourceAdapter
  15. DataSourceAdapter • Adapts a CollectionModel to the UICollectionViewDataSource protocol •

    Configuration of cells by overriding a method setting a protocol property • Added: function for creation DataSourceAdapter CollectionUpdates UICollectionView CollectionModel
  16. DataSourceAdapter public protocol CollectionCellConfigurator { func configure(cell: UICollectionViewCell, at indexPath:

    IndexPath, with model: Any) } public protocol CollectionDataSourceAdapter: class { var collectionModel: CollectionModel { get set } var cellConfigurator: CollectionCellConfigurator? { get set } var dataSource: UICollectionViewDataSource { get } } public func collectionDataSourceAdapter() -> CollectionDataSourceAdapter
  17. Usage (1) (Before) class DataSourceAdapter: CollectionDataSourceAdapter { override func configure(cell:

    UICollectionViewCell, at indexPath: IndexPath, with model: Any) { guard let cell = cell as? Cell, let model = model as? String else { return } cell.text = model } }
  18. Usage (1) (After) struct CellConfigurator: CollectionCellConfigurator { func configure(cell: UICollectionViewCell,

    at indexPath: IndexPath, with model: Any) { guard let cell = cell as? Cell, let model = model as? String else { return } cell.text = model } }
  19. Usage (2) (Before) class ViewController: UICollectionViewController { let adapter: CollectionDataSourceAdapter

    = DataSourceAdapter() override func viewDidLoad() { super.viewDidLoad() var model = CollectionModel() model.addCellWith(identifier: "Cell", model: "A") adapter.collectionModel = model collectionView?.dataSource = adapter.dataSource } … }
  20. Usage (2) (After) class ViewController: UICollectionViewController { let adapter: CollectionDataSourceAdapter

    = collectionDataSourceAdapter() override func viewDidLoad() { super.viewDidLoad() let model = collectionModel { builder in builder.addCellWith(identifier: "Cell", model: "A") } adapter.collectionModel = model adapter.cellConfigurator = CellConfigurator() collectionView?.dataSource = adapter.dataSource } …
  21. CollectionUpdates • Represents the updates between two models • Creation

    via class extension to CollectionModel • Perform updates on UICollectionView via instance method DataSourceAdapter CollectionUpdates UICollectionView CollectionModel
  22. CollectionUpdates • Represents the updates between two models • Creation

    via class protocol extension to CollectionModel • Perform updates on UICollectionView
 target protocol via instance method added through extension DataSourceAdapter CollectionUpdates UICollectionView CollectionModel
  23. CollectionUpdates public struct CollectionUpdates { let indexPathsForInsertedItems: [IndexPath] let indexPathsForDeletedItems:

    [IndexPath] let indexPathsForReloadedItems: [IndexPath] let indexPathsForMovedItems: [IndexPath: IndexPath] func performOn(_ collectionView: UICollectionView) } public extension CollectionModel { public func updates(to: CollectionModel) -> CollectionUpdates }
  24. CollectionUpdates public protocol CollectionUpdates { var indexPathsForInsertedItems: [IndexPath] { get

    } var indexPathsForDeletedItems: [IndexPath] { get } var indexPathsForReloadedItems: [IndexPath] { get } var indexPathsForMovedItems: [IndexPath: IndexPath] { get } } public extension CollectionUpdates { func performOn(_ target: CollectionUpdatesTarget) } public extension CollectionModel { public func updates(to: CollectionModel) -> CollectionUpdates }
  25. CollectionUpdatesTarget public protocol CollectionUpdatesTarget { func insertItems(at indexPaths: [IndexPath]) func

    deleteItems(at indexPaths: [IndexPath]) func reloadItems(at indexPaths: [IndexPath]) func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath) } extension UICollectionView: CollectionUpdatesTarget { }
  26. Usage (3) (Before) var toModel = … collectionView.performBatchUpdates({ let fromModel

    = self.adapter.collectionModel let updates = fromModel.updates(to: toModel) self.adapter.collectionModel = toModel updates.performOn(collectionView) }, completion: nil)
  27. Usage (3) (After) var toModel = … collectionView.performBatchUpdates({ let fromModel

    = self.adapter.collectionModel let updates = fromModel.updates(to: toModel) self.adapter.collectionModel = toModel updates.performOn(collectionView) }, completion: nil)
  28. Effects of
 Protocol Obsessed Programming • Decoupling • Easier reuse,

    easier testing • Information hiding • No concrete types exposed
  29. Effects of
 Protocol Obsessed Programming • Decoupling • Easier reuse,

    easier testing • Information hiding • No concrete types exposed • Protocols: no generics possible, but associated types
  30. Effects of
 Protocol Obsessed Programming • Decoupling • Easier reuse,

    easier testing • Information hiding • No concrete types exposed • Protocols: no generics possible, but associated types • Challenge: object creation
  31. Information Hiding: One Disadvantage • Strong reference cycle possible •

    Either ignore, or restrict protocol to classes + use weak reference • May require additional property in using class DataSourceAdapter CollectionModel Collection View Controller ✓ ✓ DataSourceAdapter CollectionModel Collection View Controller
  32. Recommendations • Only expose a concrete type if • you

    are 100% sure you don’t want to replace it with a different implementation
  33. Recommendations • Only expose a concrete type if • you

    are 100% sure you don’t want to replace it with a different implementation • you need subclassing (classes)
  34. Recommendations • Only expose a concrete type if • you

    are 100% sure you don’t want to replace it with a different implementation • you need subclassing (classes) • it is your default implementation for a protocol,
 and your protocol obsession only goes so far