Skip to content

Instantly share code, notes, and snippets.

@KaQuMiQ
Last active February 5, 2019 10:46
Show Gist options
  • Save KaQuMiQ/38fa1b78df5bce893960e2554456647d to your computer and use it in GitHub Desktop.
Save KaQuMiQ/38fa1b78df5bce893960e2554456647d to your computer and use it in GitHub Desktop.
RedSwift - Redux inspired iOS application architecture
import Foundation
public final class ModuleController<Module: ModuleDescription> {
public var uiComponent: UIComponent { return presenter.uiComponent }
private var state: Module.State
private let dispatcher: (inout Module.State, Module.Message) -> (changes: [Module.Change], tasks: [Module.Task])
private let worker: (@escaping (Module.Message) -> Void, Module.Task) -> Void
private var presenter: Module.Presenter
public init(state: Module.State,
initialize: (Module.State) -> (changes: [Module.Change], tasks: [Module.Task]),
dispatcher: @escaping (inout Module.State, Module.Message) -> (changes: [Module.Change], tasks: [Module.Task]),
worker: @escaping (@escaping (Module.Message) -> Void, Module.Task) -> Void,
presenter: Module.Presenter) {
self.state = {
let lock: NSRecursiveLock = .init()
var memory: Module.State = state
var memoryAccess: Module.State {
get {
lock.lock()
defer { lock.unlock() }
return memory
}
set {
lock.lock()
defer { lock.unlock() }
memory = newValue }
}
return memoryAccess
}()
self.dispatcher = dispatcher
self.worker = worker
self.presenter = presenter
ensureMainQueue { [weak self] in presenter.setup({ message in self?.handle(message) }) }
let (updates, tasks) = initialize(self.state)
ensureMainQueue { [weak self] in updates.forEach { self?.presenter.present($0) } }
tasks.forEach { worker(self.handle, $0) }
}
/// Perform action based on given message
public func handle(_ message: Module.Message) {
let (changes, tasks) = dispatcher(&state, message)
ensureMainQueue { [weak self] in changes.forEach { self?.presenter.present($0) } }
tasks.forEach { worker(self.handle, $0) }
}
}
fileprivate func ensureMainQueue(_ call: @escaping () -> Void) {
if Thread.isMainThread {
call()
} else {
DispatchQueue.main.async(execute: call)
}
}
public protocol ModuleDescription {
/// Controller of module based on its description
/// Controlls and binds all parts of module
typealias Controller = ModuleController<Self>
/// Typealias for handler function of this module
typealias Handler = (Message) -> Void
/// View abstraction used to present changes
typealias Presenter = ModulePresenter<Self>
/// State of module
associatedtype State
/// What changed internally (how to update view)
associatedtype Change
/// What needs to be done externally
associatedtype Task
/// What actions it allows
associatedtype Message
/// Context of existence in which module operates
/// It may pass dependencies and services
/// should not contain other controllers directly
associatedtype Context
/// Creates default presenter fore this controller
static func buildPresenter() -> Presenter
/// Tasks and display changes based on initial state
/// Called once when module controller becomes initialized
static func initialize(state: State) -> (changes: [Change], tasks: [Task])
/// Factory of workers based on given context
/// Worker executes Tasks generated in module
static func workerFactory(context: Context) -> (@escaping Handler, Task) -> Void
/// Core logic of the module
/// Consumes Message with given state that can be mutated
/// and produces internal changes and external tasks
static func dispatcher(state: inout State, message: Message) -> (changes: [Change], tasks: [Task])
/// Creating ModuleController
/// based on its description, provided context, presenter and state
static func build(context: Context, initialState: State, presenter: Presenter) -> Controller
}
extension ModuleDescription {
public static func initialize(state: State) -> (changes: [Change], tasks: [Task]) {
return (changes: [], tasks: [])
}
public static func build(context: Context, initialState: State, presenter: Presenter = Self.buildPresenter()) -> Controller {
return .init(state: initialState,
initialize: Self.initialize(state:),
dispatcher: Self.dispatcher(state:message:),
worker: Self.workerFactory(context: context),
presenter: presenter)
}
}
import UIKit
public enum UIComponent {
case window(UIWindow)
case viewController(UIViewController)
case view(UIView)
case none
}
public struct ModulePresenter<Module: ModuleDescription> {
public var uiComponent: UIComponent
public var present: (Module.Change) -> Void
public var setup: (@escaping Module.Handler) -> Void
public init(uiComponent: UIComponent,
setup: @escaping (@escaping Module.Handler) -> Void,
present: @escaping (Module.Change) -> Void) {
self.uiComponent = uiComponent
self.setup = setup
self.present = present
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment