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

CocoaHeads Ukraine

March 01, 2019
Tweet

More Decks by CocoaHeads Ukraine

Other Decks in Programming

Transcript

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

    RequestUpdateLocation: AppEvent {} struct UpdateLocation: AppEvent {} struct DidUpdateLocation: AppEvent { let timeStamp: TimeInterval let location: Result<Coordinates2D, AnyError> } •
  2. 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
  3. 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) ) } }
  4. 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
  5. 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)! } }
  6. 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
  7. 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) } }
  8. 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) <%_ } -%> } } }