architecting alive apps
TRANSCRIPT
![Page 1: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/1.jpg)
#Swift3Arch
Architecting Alive Apps
Jorge D. Ortiz Fuentes @jdortiz
![Page 2: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/2.jpg)
#Swift3CA
A Canonical Examples Production
![Page 3: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/3.jpg)
#Swift3CA
Agenda
★Advanced Architecture Background
★Application for Frameworks
★Real World Example
★Recommendations
★Recap
![Page 4: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/4.jpg)
Advanced Architecture
![Page 5: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/5.jpg)
The Classics
![Page 6: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/6.jpg)
#Swift3Arch
Clean Architecture: iOS
App Delegate
View (VC) Presenter Interactor Entity Gateway
Connector
![Page 7: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/7.jpg)
#Swift3Arch
Clean Architecture Layers
UI
DB
Preseters
Gateways
Use cases
Entities
Depen
denc
ies
![Page 8: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/8.jpg)
#Swift3CA
Viewclass 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 } }
![Page 9: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/9.jpg)
#Swift3CA
Presenterclass 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) } }
![Page 10: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/10.jpg)
#Swift3CA
Interactorclass 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() } } }
![Page 11: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/11.jpg)
#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) } }
![Page 12: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/12.jpg)
#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 } }
![Page 13: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/13.jpg)
#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) } }
![Page 14: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/14.jpg)
And What If?
![Page 15: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/15.jpg)
#Swift3CA
Interacting with Frameworks
Accounts
AddressBook
HomeKit
EventKit
HealthKit
Core Audio
Core LocationHomeKit
EventKit
![Page 16: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/16.jpg)
The Secret Sauce
![Page 17: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/17.jpg)
#Swift3CA
Injecting Dependencies
ViewPresenter
UseCaseFactory
Entity Gateway
Connector
![Page 18: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/18.jpg)
Been There, Done That
![Page 19: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/19.jpg)
Dependency Inversion Principle
High Low Abstract
Low
![Page 20: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/20.jpg)
#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() } } } }
![Page 21: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/21.jpg)
#Swift3CA
Injecting Dependencies 2
ViewPresenter
UseCaseFactory
Entity Gateway
Connector
Framework
![Page 22: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/22.jpg)
Real World Example
![Page 23: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/23.jpg)
A Prototype
![Page 24: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/24.jpg)
#Swift3CA
MeetinatorEventKit
HomeKit
Magic
![Page 25: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/25.jpg)
#Swift3CA
Yes, but…
![Page 26: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/26.jpg)
#Swift3CA
MQTTServer
Suscribe “/rooms/meeting1/colorlight”
Device
Raspberry Pi
Device
Device
![Page 27: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/27.jpg)
#Swift3CA
MQTTServer
Publish “/rooms/meeting1/colorlight”
{ “firetime”: …}
Device
Raspberry Pi
Device
Device
iPhone
![Page 28: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/28.jpg)
#Swift3CA
Another ApproachEventKit
CocoaMQTT MQTT (JSON)
Mosquitto
![Page 29: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/29.jpg)
Right Abstraction?
![Page 30: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/30.jpg)
#Swift3CA
Any* Abstraction is Better than No
Abstraction
![Page 31: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/31.jpg)
Hints for Good Abstractions
![Page 32: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/32.jpg)
#Swift3CA
No References to Framework
![Page 33: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/33.jpg)
#Swift3CA
Use Your Own Data Types
![Page 34: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/34.jpg)
#Swift3CA
Events
struct MeetingEvent { let id: String var name: String var startDate: Date var endDate: Date var hasLights: Bool }
![Page 35: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/35.jpg)
#Swift3CA
Use Your Own Communication
(Delegates/Observer/Rx…)
![Page 36: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/36.jpg)
#Swift3CA
Use Only What You Need
![Page 37: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/37.jpg)
#Swift3CA
Move Implementation Details Into
Abstracted Type
![Page 38: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/38.jpg)
Details
![Page 39: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/39.jpg)
#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
![Page 40: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/40.jpg)
#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 }
![Page 41: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/41.jpg)
#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([HMServiceTypeLightbulb]) let colorLightbulb = lightbulbs!?.first { (service) in let characteristics = service.characteristics.filter { (characteristic) in return characteristic.characteristicType !== HMCharacteristicTypeHue } return characteristics.count > 0 } return colorLightbulb } }
![Page 42: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/42.jpg)
#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)") } } } }
![Page 43: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/43.jpg)
#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 !// … } } }
![Page 44: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/44.jpg)
#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 } }
![Page 45: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/45.jpg)
Recap
![Page 46: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/46.jpg)
#Swift3CA
Recap
★ IoT is cool!
★Advanced architectures can also be applied to apps with frameworks
★Use abstractions
★YES, I mean it: Use abstractions
![Page 47: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/47.jpg)
Thank You!
![Page 48: Architecting Alive Apps](https://reader033.vdocument.in/reader033/viewer/2022042908/58f203151a28abd6668b456b/html5/thumbnails/48.jpg)
@jdortiz #Swift3CA