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

Protocol Oriented Programming

Protocol Oriented Programming

Talk from CocoaHeads Kraków January 2016.

Avatar for Radosław Cięciwa

Radosław Cięciwa

January 21, 2016

Other Decks in Programming

Transcript

  1. 3 1. Intro to protocols 2. Protocols application 3. P.O.P.

    & new swift protocols tricks 4. Summary Agenda
  2. 8 class Engineer { func readSpecification() {} func writeCode() ->

    Codebase { … } private func drinkCoffee() { } private func copyFromStackoverflow () -> Codebase { … } } Let’s Take A Close Look To Class
  3. 10 class BeginnerEngineer: Engineer { func readSpecification() { … }

    // need to be implemented func writeCode() -> Codebase { … } // need to be implemented func learn() { … } private func drinkCoffee() { … } private func copyFromStackoverflow () -> Codebase { … } } Let’s Take A Close Look To Class
  4. 11 struct ProductOwner { let engineer: Engineer // see only

    those 2 declared methods public func createAwesomeProduct() -> Product { engineer.writeCode() // coupled to behaviour not class drinkCoffee() } private func drinkCoffee() { } } Let’s Take A Close Look To Class
  5. 13 class Analytics { let kiss: KissMetricsLibrary let googleAnalytics: GoogleAnalyticsLibrary

    func sendEvent(event: Event) { ... } } Our Protocol Adaptation - Part 1
  6. 16 func sendEvent(event: Event) { if FirstTimeChecker.check(event.name) { kiss.sendKissOncePerAppLivetimeEvent(event) }

    kiss.sendKissEvent(event) googleAnalytics.sendGoogleEvent(event) apptentive.sendEvent(event) branch.sendEvent(event) } Our Protocol Adaptation - Part 1
  7. 17 Open-Close principle Objects or entities should be open for

    extension, but closed for modification. S.O.L.I.D. „O” Principle
  8. 19 extension KissMetricsLibrary: EventProcessable { func processEvent(event:Event) { … }

    } or … struct GoogleAnalyticsProcessable: EventProcessable { let ga: GoogleAnalyticsLibrary func processEvent(event:Event) { … } } Our Protocol Adaptation - Part 1
  9. 20 struct FirstTimeEventProcessable: EventProcessable { let processable: EventProcessable func processEvent(event:Event)

    { if FirstTimeChecker.check(event.name) { processable.processOncePerApp(event) } processable.processEvent(event) } } Our Protocol Adaptation - Part 1
  10. 21 class Analytics { let processes: [EventProcessable] func sendEvent(event: Event)

    { for let process in processes { process.processEvent(event) } } } Our Protocol Adaptation - Part 1
  11. 22 func sendEvent(event: Event) { for let process in processes

    { process.sendEvent(event) } } And To Compare… func sendEvent(event: Event) { if FirstTimeChecker.check(event.name) { kiss.sendKissOncePerAppLivetimeEvent(event) } kiss.sendKissEvent(event) googleAnalytics.sendGoogleEvent(event) apptentive.sendEvent(event) branch.sendEvent(event) }
  12. 23 We want to NSLog all events that are send

    to Analytics New Requirement
  13. 24 struct PrintProcess: EventProcessable { func processEvent(event:Event) { NSLog(event) }

    } class Analytics { let processes: [EventProcessable] // += PrintProcess Our Protocol Adaptation - Part 1
  14. 27 class PostsViewController: UIViewController { private let client: APIClient func

    viewDidLoad() { client.requestForData() { (listOfPosts: [FacebookPost]) in populate(listOfPosts) } } } Mocking With Protocols
  15. 28 protocol FacebookPostsRepository { func getPosts(completion: ([FacebookPost])->()) } struct APIClientFacebookPostsRepository:

    FacebookPostsRepository { let client: APIClient func getPosts(completion: ([FacebookPost])->()) { client.requestForData(completion) } } or more preferred extension APIClient: FacebookPostsRepository Mocking With Protocols
  16. 29 class PostsViewController: UIViewController { private let repository: FacebookPostsRepository func

    viewDidLoad() { repository.getPosts() { (listOfPosts: [FacebookPost]) in populate(listOfPosts) } } } Mocking With Protocols
  17. 31 - Faster development - Customization of Mock object -

    Definition outside PostsViewController class HINT: It allows you to use AbstractFactory pattern to mock large part of functionality scope. Mocking With Protocols
  18. 32 struct Navigation { init(presentable: Presentable, factory: NavigationCreatable) { …

    } func goToURL(url: NSURL) -> Bool { ... } } Testing With Protocols
  19. 33 protocol Presentable { // show view controller func show(viewController:

    UIViewController, animated: Bool) } protocol NavigationCreatable { // creates view controller from screen name func controllerForPath(screen: String) -> UIViewController? } Testing With Protocols
  20. 34 class PresentableSpy: Presentable { var wasUsed = false var

    capturedController : UIViewController? func show(viewController: UIViewController, animated: Bool) { wasUsed = true capturedController = viewController } } Testing With Protocols
  21. 35 class NavigationTests: XCTestCase { var sut: Navigation! var presentableSpy:

    PresentableSpy! // the same for CreatableSpy override func setUp() { super.setUp() presentableSpy = PresentableSpy() sut = Navigation(presentable: presentableSpy, factory: creatableSpy) } Testing With Protocols
  22. 36 func testIfPresentableWasUsedIfReturnedTrue() { let didHandle = sut.goToURL(NSURL(string: "brainly://us/fixture/42")!) XCTAssertTrue(didHandle)

    XCTAssertTrue(presentableSpy.wasUsed) } func testIfWeCreateControllerWithDetails() { sut.goToURL(NSURL(string: "brainly://us/fixture/42")!) XCTAssertTrue(navigationCreatableSpy.wasUsed) XCTAssertEqual("fixture", navigationCreatableSpy.capturedScreen) // fixture should be name of screen } Testing With Protocols
  23. 37 NavigationRouter class requirements - switch between loading and main

    app screens - handle url links to app - creating and displaying UIViewControllers (modally with custom transition) - switching between ‘tab’ like screen in app Only 200 loc (but strongly coupled code) Coupled with custom classes. Not easy to extend e.g. on iPad Extracting Code From God Classes
  24. 40

  25. 41 Principles Of P.O.P. From KrakenDev: „At the heart of

    Protocol Oriented Programming is abstraction & simplicity.” 1. Start with protocol 2. Use Value Types
  26. 43 Value types, is a type whose value is copied

    when it is assigned to a variable or constant, or when it is passed to a function. This reduces complexity by only having one owner at any point in time Value Types Vs Reference Types
  27. 45 class ErrorHandler { class func handleError(error: NSError) { let

    alert = UIAlertView(title: description, message: reason, delegate: nil, cancelButtonTitle: "OK") alert.show() } } Principles Of P.O.P.
  28. 46 client.requestForData() { (listOfPosts: [FacebookPost]) in populate(listOfPosts) }, failure: {

    [weak self] (error: NSError!) -> Void in ErrorHandler.handleError(error) } Principles Of P.O.P.
  29. 47 protocol ErrorHandable { func handleError(error: NSError) } extension ErrorHandable

    { func handleError(error: NSError) { let alert = UIAlertView(title: description, message: reason, delegate: nil, cancelButtonTitle: "OK") alert.show() } } Default Implementation
  30. 48 class PostsViewController: UIViewController, ErrorHandable { func viewDidLoad() { client.requestForData()

    { (listOfPosts: [FacebookPost]) in populate(listOfPosts) }, failure: { [weak self] (error: NSError!) -> Void in self?.handleError(error) } } } Default Implementation
  31. 50 extension ErrorHandable { func handleError(error: NSError) { let alert

    = UIAlertView(title: description, message: reason, delegate: nil, cancelButtonTitle: "OK") alert.show() } } Principles Of P.O.P. !!! “Important: UIAlertView is deprecated in iOS 8.” So probably it’s time to swap to: “UIAlertController”
  32. 51 extension ErrorHandable { func handleError(error: NSError) { let alert

    = UIAlertController(title: description, message: reason, preferredStyle: .Alert) self.presentViewController(alert, animated: true, completion: nil) } } Principles Of P.O.P. !!! Type „ErrorHandler” has no member presentViewController
  33. 52 extension ErrorHandable where Self: UIViewController { func handleError(error: NSError)

    { let alert = UIAlertController(title: description, message:reason, preferredStyle: .Alert) self.presentViewController(alert, animated: true, completion: nil) } } Principles Of P.O.P.
  34. 55 extension Engineer where Self: CorporateDeveloper { func readSpecification() {

    let fourtyTwoPagesSpecification = repository.getSpecification() // Even the variable name is long read(fourtyTwoPagesSpecification) } } Let’s Get Back To Start
  35. 56 struct CorporateDeveloper: Engineer { } Let’s Get Back To

    Start Error: Type ‘CorporateDeveloper’ does not conform to protocol ‘Engineer’
  36. 57 struct CorporateDeveloper: Engineer { func writeCode() -> Codebase {

    … } } Let’s Get Back To Start // It's ok now
  37. 59 struct CorporateDeveloper: Engineer { let departmentName: String func canDoPairProgramming(secondEng:

    Engineer) -> Bool { return departmentName == secondEng.departmentName // Error here } } Self Requirement
  38. 60 struct CorporateDeveloper: Engineer { let departmentName: String func canDoPairProgramming(secondEng:

    Engineer) -> Bool { return departmentName == (secondEng as! CorporateDeveloper).departmentName } } Self Requirement
  39. 61 // with self requirement protocol Engineer { func canDoPairProgramming(secondEng:

    Self) -> Bool } Self Requirement struct CorporateDeveloper: Engineer { let departmentName: String func canDoPairProgramming(secondEng: CorporateDeveloper) -> Bool { return departmentName == secondEng.departmentName } }
  40. 62 // if protocol have Self requirement like this protocol

    Engineer { func canDoPairProgramming(secondEng: Self) -> Bool } Self Requirement
  41. 65 // collections will be always of the same type

    - homogeneous func lunchTimeEverybody<T: Engineer>(obj: [T]) Self Requirement
  42. 66 // generic constraint could have more protocols to conform

    func lunchTimeEverybody<T where T: Engineer, T: Hungry, T: PizzaFan>(a: T) Self Requirement
  43. 67 protocol Engineer { } protocol Hungry { } protocol

    PizzaFan { } func lunchTimeEverybody<T: protocol<Engineer, Hungry, PizzaFan>>(obj: [T]) Combining Protocols In Swift typealias HungryEngineer = protocol<Engineer, Hungry, PizzaFan> func lunchTimeEverybody<T: HungryEngineer>(guys: [T]) -> Bool { ... }
  44. 70 Why Should I Use Value Types No sharable mutable

    state (we have copies) Copies are cheap Lanugage mechanisms that enforces immutability
  45. 71 If you need shared state of object Where Should

    I Use Classes UIKit classes inheritence Copying stuff will not fix your architecture
  46. 73 Keep extensions of protocol in the same file (easier

    to find) Keep extensions of class that conform to protocol in class file Good Practices
  47. 74 // !Preferred: class MyViewcontroller: UIViewController { // class stuff

    here } // MARK: - UITableViewDataSource extension MyViewcontroller: UITableViewDataSource { // table view data source methods } // !Not Preferred: class MyViewcontroller: UIViewController, UITableViewDataSource, UIScrollViewDelegate { // all methods } ref: https://github.com/raywenderlich/swift-style-guide#protocol-conformance Style Guide
  48. 75 Default implementations are not transformable to Obj-C Optional methods

    can’t be applied to structs or enumerations @objc tagged protocols cannot be applied to structs or enumerations Swift Only
  49. 76 import Foundation @objc protocol TicketServiceDelegate { optional func ticketServiceStopped(handler:

    TicketService) } Optional Methods In Swift class TicketService: NSObject { // for Obj-C conformance weak var delegate: TicketServiceDelegate? func stop() { delegate?.ticketServiceStopped?(self) } }