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.

Architecting Alive Apps

265 views

Published on

These are the slides of my talk at iOSCon 2017: https://skillsmatter.com/skillscasts/9549-architecting-alive-apps

Our apps are ever more alive. They interact with the rest of the world talking to backends and receiving notifications from them. They get their input from us and from other sensors. They are even aware of the location of the device they run in, or its position. But, in our IoT world, they may also detect presence in a room, get the temperature of it, or change the color of its lights.

Sadly enough, many of the apps available today with those capabilities have some architectural limitations:

Many of them are written in a way that is really dependent on a specific hardware.

Some restrict their use cases to whatever is provided by the hardware devices.

And almost all of them expect having a connection with the real device as the only way to test if they work properly.

However, we can also use an advanced architecture, like the Clean Architecture, to create a beautiful, scalable, testable, and robust application. Join Jorge and he will share with you how you can do it!

This is a "Code or it didn't happen" (TM) talk.

Published in: Software
  • Be the first to comment

Architecting Alive Apps

  1. 1. #Swift3Arch Architecting Alive Apps Jorge D. Ortiz Fuentes @jdortiz
  2. 2. #Swift3CA A Canonical Examples Production
  3. 3. #Swift3CA Agenda ★ Advanced Architecture Background ★ Application for Frameworks ★ Real World Example ★ Recommendations ★ Recap
  4. 4. Advanced Architecture
  5. 5. The Classics
  6. 6. #Swift3Arch Clean Architecture: iOS App Delegate View (VC) Presenter Interactor Entity Gateway Connector
  7. 7. #Swift3Arch Clean Architecture Layers UI DB Preseters Gateways Use cases Entities D ependencies
  8. 8. #Swift3CA View class AddProgrammerViewController: UITableViewController, UITextFieldDelegate { var presenter: AddProgrammerPresenter! var connector: AddProgrammerConnector! @IBOutlet weak var nameTextField: UITextField! override func viewDidLoad() { super.viewDidLoad() presenter.viewReady() } @IBAction func cancel(_ sender: Any) { presenter.cancel() } @IBAction func save(_ sender: Any) { presenter.save() } } extension AddProgrammerViewController: AddProgrammerView { func display(title: String) { self.title = title } func enableSaveButton(_ enable: Bool) { saveButton.isEnabled = enable } }
  9. 9. #Swift3CA Presenter class AddProgrammerPresenter { private let useCaseFactory: UseCaseFactory weak var view: AddProgrammerView! private var programmer = ProgrammerRequest() { didSet { updateView() } } init(useCaseFactory: UseCaseFactory) { self.useCaseFactory = useCaseFactory } func viewReady() { configureView() } private func updateView() { showTitle() !// … configureSaveAbility() } private func configureView() { setUpName() !// … updateView() } private func showTitle() { view.display(title: programmer.name) } }
  10. 10. #Swift3CA Interactor class AddProgrammerUseCase { fileprivate let entityGateway: EntityGateway fileprivate let request: ProgrammerRequest fileprivate let completion: AddProgrammerCompletion init(entityGateway: EntityGateway, request: ProgrammerRequest, completion: @escaping AddProgrammerCompletion) { self.entityGateway = entityGateway self.request = request self.completion = completion } } extension AddProgrammerUseCase: UseCase { func execute() { let programmer = Programmer(…) entityGateway.create(programmer: programmer) { self.completion() } } }
  11. 11. #Swift3CA Entity Gateway class InMemoryRepo { var fetchNotifier: FetchProgrammersCompletion? fileprivate var programmers = […] } extension InMemoryRepo: EntityGateway { func create(programmer: Programmer, completion: @escaping CreateProgrammerCompletion) { programmers.append(programmer) completion() self.fetchNotifier?(programmers) } }
  12. 12. #Swift3CA Connector class AddProgrammerConnector { let entityGateway: EntityGateway init(entityGateway: EntityGateway) { self.entityGateway = entityGateway } func assembleModule(view: AddProgrammerViewController) { let useCaseFactory = UseCaseFactory(entityGateway: entityGateway) let presenter = AddProgrammerPresenter(useCaseFactory: useCaseFactory) view.presenter = presenter view.connector = self presenter.view = view } }
  13. 13. #Swift3CA Factory class UseCaseFactory { let entityGateway: EntityGateway init(entityGateway: EntityGateway) { self.entityGateway = entityGateway } func addProgrammerUseCase(request: ProgrammerRequest, completion: @escaping AddProgrammerCompletion) !-> UseCase { return AddProgrammerUseCase(entityGateway: entityGateway, request: request, completion: completion) } }
  14. 14. And What If?
  15. 15. #Swift3CA Interacting with Frameworks Accounts AddressBook HomeKit EventKit HealthKit Core Audio Core Location HomeKit EventKit
  16. 16. The Secret Sauce
  17. 17. #Swift3CA Injecting Dependencies View Presenter UseCaseFactory Entity Gateway Connector
  18. 18. Been There, Done That
  19. 19. Dependency Inversion Principle High LowAbstract Low
  20. 20. #Swift3CA CloudKit ★ This is data source using CloudKit ★ CloudKit is an implementation detail ★ NO leaky abstractions class CloudKitRepo { let database: CKDatabase let programmerRecord = "Programmer" init() { let container = CKContainer.default() database = container.database(with: .private) } fileprivate func recordFrom(programmer: Programmer) !-> CKRecord { let programmerID = CKRecordID(recordName: programmer.id) let record = CKRecord(recordType: programmerRecord, recordID: programmerID) updateRecordProperties(record: record, programmer: programmer) return record } } extension CloudKitRepo: EntityGatewayProtocol { func create(programmer: Programmer, completion:@escaping () !-> Void) { let record = recordFrom(programmer: programmer) database.save(record) { record, error in guard error !== nil else { NSLog("Save error: (error!?.localizedDescription)") return } DispatchQueue.main.sync { completion() } } } }
  21. 21. #Swift3CA Injecting Dependencies 2 View Presenter UseCaseFactory Entity Gateway Connector Framework
  22. 22. Real World Example
  23. 23. A Prototype
  24. 24. #Swift3CA Meetinator EventKit HomeKit Magic
  25. 25. #Swift3CA Yes, but…
  26. 26. #Swift3CA MQTT Server Suscribe “/rooms/meeting1/colorlight” Device Raspberry Pi Device Device
  27. 27. #Swift3CA MQTT Server Publish “/rooms/meeting1/colorlight” { “firetime”: …} Device Raspberry Pi Device Device iPhone
  28. 28. #Swift3CA Another Approach EventKit CocoaMQTT MQTT (JSON) Mosquitto
  29. 29. Right Abstraction?
  30. 30. #Swift3CA Any* Abstraction is Better than No Abstraction
  31. 31. Hints for Good Abstractions
  32. 32. #Swift3CA No References to Framework
  33. 33. #Swift3CA Use Your Own Data Types
  34. 34. #Swift3CA Events struct MeetingEvent { let id: String var name: String var startDate: Date var endDate: Date var hasLights: Bool }
  35. 35. #Swift3CA Use Your Own Communication (Delegates/Observer/Rx…)
  36. 36. #Swift3CA Use Only What You Need
  37. 37. #Swift3CA Move Implementation Details Into Abstracted Type
  38. 38. Details
  39. 39. #Swift3CA From Date to JSON ★ MQTT messages contained JSON ★ firetime is a JSON format date let formatter = ISO8601DateFormatter() let command: [ String: String ] = [ "firetime": formatter.string(from: fireDate), "type": type.mqttActionType(), ] let jsonData = try! JSONSerialization.data(withJSONObject : command, options: JSONSerialization.WritingOptions()) as Data
  40. 40. #Swift3CA Picky with Dates ★ HomeKit HMTimeTrigger only accepts times with seconds = 0 private func fixFireDate(_ fireDate: Date) !-> Date { let calendar = Calendar.current let fixedFireDate = calendar.nextDate(after: fireDate, matching: DateComponents(second: 0), matchingPolicy: .nextTime)! return fixedFireDate }
  41. 41. #Swift3CA Browse HomeKit ★ HomeKit offers several abstractions in a hierarchy • Homes • Rooms • Accessories • Services • Triggers ★ Extract what you need class HomeKitColorLight: NSObject, LightController { var delegate: LightControllerDelegate? fileprivate let homeManager: HMHomeManager fileprivate var primaryHome: HMHome? func homeManagerDidUpdateHomes(_ manager: HMHomeManager) { primaryHome = homeManager.primaryHome delegate!?.lightControllerReady(self) } private func searchFirstColorLight() !-> HMService? { let lightbulbs = primaryHome!?.servicesWithTypes([HMServiceTypeLightb ulb]) let colorLightbulb = lightbulbs!?.first { (service) in let characteristics = service.characteristics.filter { (characteristic) in return characteristic.characteristicType !== HMCharacteristicTypeHue } return characteristics.count > 0 } return colorLightbulb } }
  42. 42. #Swift3CA Look for Events ★ EventKit allows multiple calendars ★ Avoid that complexity fileprivate func fetchMeetingCalendar() { guard status !== .ready else { return } let calendars = eventStore.calendars(for: .event) let calendar = calendars.filter { $0.title !== meetingCalendarTitle } .first if let calendar = calendar { meetingCalendar = calendar } else { meetingCalendar = EKCalendar(for: .event, eventStore: eventStore) if let meetingCalendar = meetingCalendar { meetingCalendar.title = meetingCalendarTitle meetingCalendar.source = eventStore.defaultCalendarForNewEvents.source do { try eventStore.saveCalendar(meetingCalendar, commit: true) } catch let error as NSError { NSLog("Error: (error)") } } } }
  43. 43. #Swift3CA Authorization ★ EventKit requires Authorization to access the data class EventKitEventProvider { enum Status { case ready, accessDenied, unknown } let eventStore: EKEventStore var status = Status.unknown init() { eventStore = EKEventStore() checkAccessToEvents() fetchMeetingCalendar() } private func checkAccessToEvents() { switch EKEventStore.authorizationStatus(for: .event) { case .authorized: status = .ready case .notDetermined: !// First time access requestAccessToEvents() case .denied, .restricted: status = .accessDenied } } private func requestAccessToEvents() { eventStore.requestAccess(to: .event) { (granted: Bool, error: Error?) in !// … } } }
  44. 44. #Swift3CA extension LightControllerAction { func homeKitColor() !-> UIColor { let color: UIColor switch(self) { case .start: color = UIColor.green case .warn: color = UIColor.orange case .end: color = UIColor.red case .off: color = UIColor.black } return color } func sceneName() !-> String { let name: String switch(self) { case .start: name = "Go green" case .warn: name = "Go orange" case .end: name = "Go red" case .off: name = "Go off" } return name } } Extend like a Boss extension LightControllerAction { func mqttActionType() !-> String { let action: String switch self { case .start: action = "start" case .warn: action = "warn" case .end: action = "end" case .off: action = "off" } return action } }
  45. 45. Recap
  46. 46. #Swift3CA Recap ★ IoT is cool! ★ Advanced architectures can also be applied to apps with frameworks ★ Use abstractions ★ YES, I mean it: Use abstractions
  47. 47. Thank You!
  48. 48. @jdortiz #Swift3CA

×