Last active
February 5, 2019 10:46
-
-
Save KaQuMiQ/38fa1b78df5bce893960e2554456647d to your computer and use it in GitHub Desktop.
RedSwift - Redux inspired iOS application architecture
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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