Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Why the Dark Side should use Swift and a SOLID Architecture

207 views

Published on

Everybody involved in a mid size project or bigger is concerned about architecture. We have all been told that a good architecture can help us to have a more agile, robust, and easier to work with application, but it is not always clear how nor the architecture everybody else is talking about. By establishing parallelisms with the very well known problems of the Death Stars and the Starkiller Base, I go through the most relevant problems, explain how a well thought out architecture can be valuable to us, and share some code that implements it. The talk includes only minor spoilers, it will focus on more than MVC. It uses Swift in all of the code examples, because it is very well suited to implement those advanced architectures.
Take away concepts:
Architectural concepts for mobile applications
How to use some common design patterns
How to improve the testability of your code
Take advantage of Swift features

Published in: Software
  • Be the first to comment

  • Be the first to like this

Why the Dark Side should use Swift and a SOLID Architecture

  1. 1. Why the Dark Side should use Swift and a SOLID Architecture Jorge D. Ortiz-Fuentes @jdortiz #SwiftDarkSide
  2. 2. A Canonical Examples production #SwiftDarkSide
  3. 3. #SwiftDarkSide Agenda The Weaknesses of the Dark Side SOLID Architecture Design patterns
  4. 4. Weaknesses of the Dark Side
  5. 5. Conspiracy Theory
  6. 6. Agility and the MVP
  7. 7. Moving a Planet
  8. 8. SPoF
  9. 9. Whose blame is that?
  10. 10. Framework dependency
  11. 11. Teams can hardly work together
  12. 12. Do you see any problems?
  13. 13. #SwiftDarkSide Needs to Address Fast growth Robustness • testable • decoupled • debuggable (blame) Team collaboration Reusable Defer decisions Replaceable frameworks
  14. 14. There is a way: SOLID Principles
  15. 15. AKA Clean Architecture
  16. 16. I find your lack of faith disturbing
  17. 17. SOLID Architecture
  18. 18. Clean Architecture AppDelegate View (VC) Presenter Interactor Entity Gateway Connector
  19. 19. #SwiftDarkSide Single Responsibility MVC is not enough More classes, but more cohesive: only one reason to change
  20. 20. #SwiftDarkSide Business Logic import Foundation class ShowAllSpeakersInteractor { // MARK: - Properties let entityGateway: EntityGatewayFetchSpeakersProtocol weak var presenter: SpeakersListPresenterProtocol? // MARK: - Initializers init(entityGateway: EntityGatewayFetchSpeakersProtocol) { self.entityGateway = entityGateway } } extension ShowAllSpeakersInteractor: InteractorCommandProtocol { func execute() { let entities = entityGateway.fetchAllSpeakers() let displayData = entities.map({entity in return SpeakerDisplayData(speaker: entity)}) presenter?.presentAllSpeakers(displayData) } }
  21. 21. #SwiftDarkSide Open Close Open to Extension, Closed to Modification
  22. 22. #SwiftDarkSide class SpeakersTableViewController: UITableViewController, SegueHandlerTypeProtocol { static let speakerCellIdentifier = "SpeakerCell" var eventHandler: SpeakersListEventHandlerProtocol? var numberOfRows = 0 override func viewDidLoad() { super.viewDidLoad() eventHandler?.viewDidLoad() } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return numberOfRows } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier(SpeakersTa bleViewController.speakerCellIdentifier, forIndexPath: indexPath) as! SpeakerTableViewCell eventHandler?.presentCell(cell, indexPath: indexPath) return cell } } extension SpeakersTableViewController: SpeakersListViewProtocol { func configureListWithNumberOfRows(numberOfRows : Int) { self.numberOfRows = numberOfRows } func addRowsAtIndexPaths(indexPaths: [NSIndexPath]) { self.tableView.insertRowsAtIndexPaths(inde xPaths, withRowAnimation: UITableViewRowAnimation.Automatic) } } Passive View
  23. 23. #SwiftDarkSide Liskov Substitution It should be possible to use derived classes without special care
  24. 24. #SwiftDarkSide extension SpeakersListsPresenter: SpeakersListEventHandlerProtocol { func viewDidLoad() { interactor.execute() } func presentCell(cell: SpeakerCellProtocol, indexPath: NSIndexPath) { let index = indexPath.row guard index < speakers.count else { return } let speaker = speakers[index] cell.displayName(speaker.name) cell.displayTitle(speaker.title) cell.displayDateSubmitted(relativeDateStrin gFromDate(speaker.dateSubmitted)) } } Presentation class SpeakersListsPresenter { weak var view: SpeakersListViewProtocol? let interactor: ShowAllSpeakersInteractor private var speakers: [SpeakerDisplayData]=[] // MARK: - Initializer init(interactor: ShowAllSpeakersInteractor, deleteInteractor: DeleteSpeakerInteractorProtocol) { self.interactor = interactor } } extension SpeakersListsPresenter: SpeakersListPresenterProtocol { func presentAllSpeakers(speakers: [SpeakerDisplayData]) { view?.configureListWithNumberOfRows(speakers.count) self.speakers = speakers let addedIndexPaths = speakers.enumerate() .map({(index, _) in return NSIndexPath(forRow: index, inSection: 0)}) view?.addRowsAtIndexPaths(addedIndexPaths) } }
  25. 25. #SwiftDarkSide Interface Segregation Don’t force a class to depend on methods that it won’t use One interactor will not likely use every functionality of the entity gateway
  26. 26. #SwiftDarkSide class InMemorySpeakersRepo { // MARK: - Properties var speakers: [Speaker] = [] init() { } } extension InMemorySpeakersRepo: EntityGatewayFetchSpeakersProtocol { func fetchAllSpeakers() -> [Speaker] { return speakers } } extension InMemorySpeakersRepo: EntityGatewayCreateSpeakerProtocol { func createSpeaker(name name: String, title: String, synopsis: String, dateSubmitted: NSDate) { let id = IdentityGenerator.newUUID() speakers.append(Speaker(id: id, name: name, title: title, synopsis: synopsis, dateSubmitted: dateSubmitted)) } } extension InMemorySpeakersRepo: EntityGatewayDeleteSpeakerProtocol { func deleteSpeaker(id: String) { speakers = speakers.filter { speaker in speaker.id != id } } } Different Functions of the Entity Gateway
  27. 27. #SwiftDarkSide Dependency Inversion The business case SHOULDN’T depend on the frameworks
  28. 28. #SwiftDarkSide Deletion Use Case class DeleteSpeakerInteractor { let entityGateway: EntityGatewayDeleteSpeakerProtocol var id: String? init(entityGateway: EntityGatewayDeleteSpeakerProtocol) { self.entityGateway = entityGateway } } extension DeleteSpeakerInteractor : InteractorCommandProtocol { func execute() { guard let id = self.id else { return } entityGateway.deleteSpeaker(id) } }
  29. 29. Design Patterns
  30. 30. Take me Outta Here func presentCell(cell: SpeakerCellProtocol, indexPath: NSIndexPath) { let index = indexPath.row guard index < speakers.count else { return } let speaker = speakers[index] cell.displayName(speaker.name) cell.displayTitle(speaker.title) cell.displayDateSubmitted(relativeDateStringFromDate(s peaker.dateSubmitted)) } Know That!
  31. 31. var control = Houston(fuel: 1.0, astronaut: nil, spaceshipOK: true) do { try control.launchSpaceship() } catch Houston.LaunchError.NoFuel { // Add Fuel print("Adding fuel") } catch Houston.LaunchError.NoAstronaut { print("Next in line") } catch Houston.LaunchError.BrokenShip(let problem) { print(problem) } catch let unknowError { // } Ready for Life class Houston { let fuel: Double let astronaut: String let spaceshipOK: Bool init (fuel: Double, astronaut: String?, spaceshipOK: Bool) { self.fuel = fuel self.astronaut = astronaut ?? "" self.spaceshipOK = spaceshipOK } enum LaunchError: ErrorType { case NoFuel, NoAstronaut, BrokenShip(String) } func launchSpaceship() throws { guard fuel >= 1.0 else { throw LaunchError.NoFuel } guard astronaut != "" else { throw LaunchError.NoAstronaut } guard spaceshipOK else { throw LaunchError.BrokenShip("Engine") } print("Launching spaceship") } } Know That!
  32. 32. class Text { func displayContents() { print("Hola") } } class NullText: Text { override func displayContents() { print(“Not much here") } } func fetchText() -> Text { return NullText() } let text = fetchText() text.displayContents() Null Object class Text { func displayContents() { print("Hola") } } func fetchText() -> Text? { return nil } if let text = fetchText() { text.displayContents() }
  33. 33. Poor Template class PoorTrooper { func getIntoSpaceShip() { print("Commencing countdown engines on") } func conquer(target: String) { fatalError() } func comeBack() { print("Planet earth is blue") } func attack(target: String) { getIntoSpaceShip() conquer(target) comeBack() } } let cloneTrooper = PoorTrooper() cloneTrooper.attack("Alderaan")
  34. 34. Swifty Template protocol Trooper { func getIntoSpaceShip() func conquer(target: String) func comeBack() func attack(target: String) } extension Trooper { func getIntoSpaceShip() { print("Commencing countdown engines on") } func comeBack() { print("Planet earth is blue") } func attack(target: String) { getIntoSpaceShip() conquer(target) comeBack() } } class StormTrooper: Trooper { func conquer(target: String) { print("Become part of the Empire, (target)") } } let finn = StormTrooper() finn.attack("Tatooine")
  35. 35. Next Steps
  36. 36. #SwiftDarkSide Recommendations Pay attention to your architecture; It always pays off Use Principles to take decisions Take advantage of the language and adapt the patterns to it In case of doubt, ask for an architecture loan and ship it
  37. 37. canonicalexamples.com coupon: APPSTERDAMERS16
  38. 38. Thank you!
  39. 39. @jdortiz #SwiftDarkSide

×