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

The benefits of MQTT in remote debugging

The benefits of MQTT in remote debugging

Talk by Danil Lisovoy.

Данил расскажет, почему удобно писать на Redux, и покажет как легко с помощью MQTT добиться тестируемости своей архитектуры на Redux.

This talk was made for CocoaFriday #1 ( https://cocoaheads.org.ua/cocoafriday/1 ) which took place Mar 1, 2019.

Video is available here: https://youtu.be/3HGou0SS1qQ

Db84cf61fdada06b63f43f310b68b462?s=128

CocoaHeads Ukraine

March 01, 2019
Tweet

More Decks by CocoaHeads Ukraine

Other Decks in Programming

Transcript

  1. The benefits of MQTT in remote debugging

  2. Redux

  3. None
  4. State Store Action State Reduce

  5. Actions struct DidChangeLocationPermission: AppEvent { let access: Bool } struct

    RequestUpdateLocation: AppEvent {} struct UpdateLocation: AppEvent {} struct DidUpdateLocation: AppEvent { let timeStamp: TimeInterval let location: Result<Coordinates2D, AnyError> } •
  6. import Foundation import ReactiveSwift import Result public protocol Defaultable {

    static var defaultValue: Self { get } } infix operator <~ : BindingPrecedence open class Store<State, Event> { public typealias Reducer = (State, Event) -> State fileprivate var innerProperty: MutableProperty<State> fileprivate var reducers: [Reducer] public required init(state: State, reducers: [Reducer]) { self.innerProperty = MutableProperty<State>(state) self.reducers = reducers } public func consume(event: Event) { self.innerProperty.value = reducers.reduce(self.innerProperty.value) { $1($0, event) } } } extension Store: PropertyProtocol { public var value: State { return innerProperty.value } public var producer: SignalProducer<State, NoError> { return innerProperty.producer } public var signal: Signal<State, NoError> { return innerProperty.signal } } public extension Store { @discardableResult public static func <~ <Source: BindingSource> (target: Store<State, Event>, source: Source) -> Disposable? where Event == Source.Value { return source.producer .take(during: target.innerProperty.lifetime) .startWithValues(target.consume) } } Store
  7. struct AppState: Encodable { let location: AppLocation let weather: AppWeather

    let searching: AppSearch let sync: AppSync let photos: AppPhotos } extension AppState: Defaultable { static var defaultValue = AppState( location: .defaultValue, weather: .defaultValue, searching: .defaultValue, sync: .defaultValue, photos: .defaultValue) } extension AppState { static func reudce(_ state: AppState, _ event: AppEvent) -> AppState { return AppState( location: AppLocation.reudce(state.location, event), weather: AppWeather.reudce(state.weather, event), searching: AppSearch.reudce(state.searching, event), sync: AppSync.reduce(state.sync, event), photos: AppPhotos.reduce(state.photos, event) ) } }
  8. struct AppLocation: Encodable, Equatable { let availability: Availability; enum Availability:

    String, Encodable, Equatable { case notYetRequested case requested case notAvailable case available } let deviceLocation: DeviceLocation; enum DeviceLocation: AutoEncodable, Equatable { case none case updating case success(location: Coordinates2D, timestamp: TimeInterval) case error(value: AnyError) } } AppLocation
  9. ViewModel View Render O bserves Service

  10. ViewModel View Render O bserves Store

  11. State Service Action Dispatches O bserves State Store API

  12. Action creator Action Action Action Store Service

  13. MQTT What about debugging?

  14. Broker P P P C C C

  15. Is it secure?

  16. P State Store UI Props B B B Action

  17. Broker Actions Props State Examples

  18. Protocol import ReactiveSwift import Swinject import struct Result.AnyError import enum

    Result.NoError protocol MQTTServiceProtocol { func publish<T>(in channel: String) -> Action<T, (), AnyError> where T: Encodable func subscribe<T>(to channel: String) -> Signal<T, NoError> where T: Decodable } protocol MQTTServiceAccessible { var mqttService: MQTTServiceProtocol { get } } extension MQTTServiceAccessible { var mqttService: MQTTServiceProtocol { return Container.current .resolve(MQTTServiceProtocol.self)! } }
  19. extension MQTTService: MQTTServiceProtocol { func publish<T>(in channel: String) -> Action<T,

    (), AnyError> where T: Encodable { return Action(execute: publishClosure(in: channel)) } func publishClosure<T>(in channel: String) -> (T) -> SignalProducer<(), AnyError> where T: Encodable { return { [weak self] message in var data: Data guard let self = self else { return .empty } do { data = try JSONEncoder().encode(message) } catch { return SignalProducer(error: AnyError(error)) } self.client.publish(.init( topic: channel, payload: .init(data))) return .empty } } ..
 } Store B
  20. Store B class MQTTMiddleware: StoreMiddleware, MQTTServiceAccessible { private let flags:

    LoggerFlags private let name: String private lazy var logEvent: Action<AnyEvent, (), AnyError> = mqttService.publish(in: "\(name)/store/event") private lazy var logState: Action<AppState, (), AnyError> = mqttService.publish(in: "\(name)/store/state") private let events = Signal<AppEvent, NoError>.pipe() private let states = Signal<AppState, NoError>.pipe() public init( flags: LoggerFlags = .logAll, name: String = "undefined" ) { self.flags = flags self.name = name setupObserving() } func consume<Event>(event: Event) -> SignalProducer<Event, NoError>? { return SignalProducer(value: TypeDispatcher.value(event) .dispatch { (event: AppEvent) in events.input.send(value: event) } .extract() ) } func stateDidChange<State>(state: State) { TypeDispatcher.value(state) .dispatch { (state: AppState) in states.input.send(value: state) } } func unsafeValue() -> Signal<Any, NoError>? { return nil } } private typealias Observing = MQTTMiddleware private extension Observing { func setupObserving() { logEvent <~ events.output.throttle(0.5, on: QueueScheduler.main).map(AnyEvent.init) logState <~ states.output.throttle(0.5, on: QueueScheduler.main) } }
  21. Sourcery import Foundation <% func camelCased(_ string: String) -> String

    { return "\(String(string.first!).lowercased())\(String(string.dropFirst()))".replacingOccurrences(of: ".", with: "") } func allEvents() -> [Type] { return types.all.filter { type in (type is Struct || type is Enum) && (type.implements["AppEvent"] != nil) } } -%> enum AnyEvent: Codable { <%_ for type in allEvents() { -%> case <%= camelCased(type.name) %>(<%= type.name %>) <%_ } -%> init(_ event: AppEvent) { switch event { <%_ for type in allEvents() { -%> case let event as <%= type.name %>: self = .<%= camelCased(type.name) %>(event) <%_ } -%> default: fatalError("Unexpected event") } } var event: AppEvent { switch self { <%_ for type in allEvents() { -%> case let .<%= camelCased(type.name) %>(event): return event <%_ } -%> } } enum Keys: CodingKey { case type, event } enum EventType: String, Codable { <%_ for type in allEvents() { -%> case <%= camelCased(type.name) %> <%_ } -%> } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: Keys.self) let type = try container.decode(EventType.self, forKey: .type) switch type { <%_ for type in allEvents() { -%> case .<%= camelCased(type.name) %>: self = .<%= camelCased(type.name) %>(try container.decode(<%= type.name %>.self, forKey: .event)) <%_ } -%> } } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: Keys.self) switch self { <%_ for type in allEvents() { -%> case .<%= camelCased(type.name) %>(let event): try container.encode(EventType.<%= camelCased(type.name) %>, forKey: .type) try container.encode(event, forKey: .event) <%_ } -%> } } }
  22. Demo Weather App

  23. Questions?