Skip to content

Instantly share code, notes, and snippets.

@alexdrone
Created January 22, 2017 13:42
Show Gist options
  • Save alexdrone/a30b7a1908518df73194572ccaa114d5 to your computer and use it in GitHub Desktop.
Save alexdrone/a30b7a1908518df73194572ccaa114d5 to your computer and use it in GitHub Desktop.
Unidirectional data-flow in Swift with Dependency Injection.
protocol AnyStateful: class {
/// The children of this node.
var statefulChildren: [AnyStateful] { get }
// Internal call to propagate the render call.
func _render(state: Any?)
/// The controller that is used as delegate for this stateful object.
weak var _controller: StatefulControllerType? { get set }
}
extension AnyStateful {
/// Assign and propagates down the controller.
func assign(controller: StatefulControllerType?) {
self._controller = controller
self.statefulChildren.forEach { $0.assign(controller: controller) }
}
}
protocol InjectionContextType { }
protocol InjectableState: Stateful, AnyInjectable {
associatedtype C: InjectionContextType
/// The context for this node.
var context: C { get set }
/// Initialise the class with the context passed as argument.
init(context: C)
/// Called when a new context is being injected.
func onContextChange(_ context: C?)
}
extension InjectableState {
var contextType: Any.Type {
return C.self
}
/// Recursively inject the new context.
func inject(context: C) {
self.context = context
self.onContextChange(context)
self.statefulChildren.flatMap {
$0 as? AnyInjectable
}.filter {
$0.contextType == C.self
}.forEach {
$0._inject(context: context)
}
}
// Private type-erased injector.
func _inject(context: InjectionContextType) {
guard let context = context as? C else {
return
}
self.inject(context: context)
}
}
protocol AnyInjectable {
var contextType: Any.Type { get }
func _inject(context: InjectionContextType)
}
protocol StatefulControllerType: class { }
protocol Stateful: AnyStateful {
associatedtype S
associatedtype D: StatefulControllerType
/// Update 'this' object with the current state passed as argument.
func onRender(state: S?)
}
extension Stateful {
var controller: D? {
return self._controller as? D
}
// Internal call to propagate the render call.
func _render(state: Any?) {
if state == nil {
self.render(state: nil)
return
}
guard let state = state as? S else {
return
}
render(state: state)
}
/// Recursively render the state.
func render(state: S?) {
self.onRender(state: state)
self.statefulChildren.forEach { $0._render(state: state) }
}
}
extension Stateful where Self: UIView {
/// In the case of 'UIView' the 'render' command gets propagated to its subviews.
var statefulChildren: [AnyStateful] {
return self.subviews.flatMap { $0 as? AnyStateful }
}
}
extension Stateful where Self: UIViewController {
/// In the case of 'UIViewController' the 'render' command gets propagated to its child-vcs.
var statefulChildren: [AnyStateful] {
return self.childViewControllers.flatMap { $0 as? AnyStateful }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment