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. The benefits of MQTT
    in remote debugging

    View Slide

  2. Redux

    View Slide

  3. View Slide

  4. State
    Store
    Action
    State
    Reduce

    View Slide

  5. Actions
    struct DidChangeLocationPermission: AppEvent {
    let access: Bool
    }
    struct RequestUpdateLocation: AppEvent {}
    struct UpdateLocation: AppEvent {}
    struct DidUpdateLocation: AppEvent {
    let timeStamp: TimeInterval
    let location: Result
    }

    View Slide

  6. import Foundation
    import ReactiveSwift
    import Result
    public protocol Defaultable {
    static var defaultValue: Self { get }
    }
    infix operator <~ : BindingPrecedence
    open class Store {
    public typealias Reducer = (State, Event) -> State
    fileprivate var innerProperty: MutableProperty
    fileprivate var reducers: [Reducer]
    public required init(state: State, reducers: [Reducer]) {
    self.innerProperty = MutableProperty(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 {
    return innerProperty.producer
    }
    public var signal: Signal {
    return innerProperty.signal
    }
    }
    public extension Store {
    @discardableResult
    public static func <~ (target: Store, source: Source) -> Disposable?
    where Event == Source.Value
    {
    return source.producer
    .take(during: target.innerProperty.lifetime)
    .startWithValues(target.consume)
    }
    }
    Store

    View Slide

  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)
    )
    }
    }

    View Slide

  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

    View Slide

  9. ViewModel
    View
    Render
    O
    bserves
    Service

    View Slide

  10. ViewModel
    View
    Render
    O
    bserves
    Store

    View Slide

  11. State
    Service
    Action
    Dispatches
    O
    bserves
    State
    Store
    API

    View Slide

  12. Action
    creator
    Action
    Action
    Action
    Store
    Service

    View Slide

  13. MQTT
    What about debugging?

    View Slide

  14. Broker
    P
    P
    P
    C
    C
    C

    View Slide

  15. Is it secure?

    View Slide

  16. P
    State
    Store UI
    Props
    B
    B
    B
    Action

    View Slide

  17. Broker
    Actions
    Props
    State
    Examples

    View Slide

  18. Protocol
    import ReactiveSwift
    import Swinject
    import struct Result.AnyError
    import enum Result.NoError
    protocol MQTTServiceProtocol {
    func publish(in channel: String)
    -> Action where T: Encodable
    func subscribe(to channel: String)
    -> Signal where T: Decodable
    }
    protocol MQTTServiceAccessible {
    var mqttService: MQTTServiceProtocol { get }
    }
    extension MQTTServiceAccessible {
    var mqttService: MQTTServiceProtocol {
    return Container.current
    .resolve(MQTTServiceProtocol.self)!
    }
    }

    View Slide

  19. extension MQTTService: MQTTServiceProtocol {
    func publish(in channel: String)
    -> Action
    where T: Encodable
    {
    return Action(execute: publishClosure(in: channel))
    }
    func publishClosure(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

    View Slide

  20. Store
    B
    class MQTTMiddleware: StoreMiddleware, MQTTServiceAccessible {
    private let flags: LoggerFlags
    private let name: String
    private lazy var logEvent: Action =
    mqttService.publish(in: "\(name)/store/event")
    private lazy var logState: Action =
    mqttService.publish(in: "\(name)/store/state")
    private let events = Signal.pipe()
    private let states = Signal.pipe()
    public init(
    flags: LoggerFlags = .logAll,
    name: String = "undefined"
    )
    {
    self.flags = flags
    self.name = name
    setupObserving()
    }
    func consume(event: Event) -> SignalProducer? {
    return SignalProducer(value:
    TypeDispatcher.value(event)
    .dispatch { (event: AppEvent) in events.input.send(value: event) }
    .extract()
    )
    }
    func stateDidChange(state: State) {
    TypeDispatcher.value(state)
    .dispatch { (state: AppState) in states.input.send(value: state) }
    }
    func unsafeValue() -> Signal? { 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)
    }
    }

    View Slide

  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)
    <%_ } -%>
    }
    }
    }

    View Slide

  22. Demo
    Weather App

    View Slide

  23. Questions?

    View Slide