-
-
Save srea/83727c71d5e5292c6d99c490e133b2e9 to your computer and use it in GitHub Desktop.
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
// | |
// Plugin.swift | |
// HomeSecurity | |
// | |
// Created by Valery.Kokanov on 20/12/2018. | |
// Copyright © 2018 Ooma Inc. All rights reserved. | |
// | |
import RIBs | |
import RxSwift | |
// MARK: - Plugin | |
protocol Plugin: Equatable { | |
associatedtype ListenerType: Interactable | |
associatedtype ComponentType: Dependency | |
associatedtype Context: Any | |
associatedtype ReturnType = ViewableRouting | |
var killSwitchName: String { get } | |
func isApplicable(_ context: Context) -> Observable<Bool> | |
func createRIB(component: ComponentType, listener: ListenerType) -> ReturnType | |
} | |
extension Plugin { | |
public static func == (lhs: Self, rhs: Self) -> Bool { | |
return lhs.killSwitchName == rhs.killSwitchName | |
} | |
} | |
final class BoxPlugin<ComponentType: Dependency, ListenerType: Interactable, Context>: Plugin { | |
private let _createRIB: (ComponentType, ListenerType) -> Any | |
private let _killSwitchName: String | |
private let _isApplicable: (Context) -> Observable<Bool> | |
var killSwitchName: String { | |
return _killSwitchName | |
} | |
init<P: Plugin>(_ plugin: P) where | |
P.ListenerType == ListenerType, | |
P.ComponentType == ComponentType, | |
P.Context == Context | |
{ | |
_createRIB = plugin.createRIB | |
_killSwitchName = plugin.killSwitchName | |
_isApplicable = plugin.isApplicable | |
} | |
func isApplicable(_ context: Context) -> Observable<Bool> { | |
return _isApplicable(context) | |
} | |
func createRIB(component: ComponentType, listener: ListenerType) -> ViewableRouting { | |
return _createRIB(component, listener) as! ViewableRouting | |
} | |
} | |
// MARK: - Plugins Holder | |
private class PluginsHolder<ComponentType: Dependency, ListenerType: Interactable, Context> { | |
private let component: ComponentType | |
private let listener: ListenerType | |
private var boxedPlugins: [BoxPlugin<ComponentType, ListenerType, Context>] | |
final var pluginsNames: [String] { | |
return boxedPlugins.map { $0.killSwitchName } | |
} | |
final var plugins: [String: BoxPlugin<ComponentType, ListenerType, Context>] { | |
return boxedPlugins.reduce(into: [String: BoxPlugin<ComponentType, ListenerType, Context>]()) { buffer, next in | |
buffer[next.killSwitchName] = next | |
} | |
} | |
private(set) final var applicablePlugins: [String: BoxPlugin<ComponentType, ListenerType, Context>] = [:] | |
init( | |
component: ComponentType, | |
listener: ListenerType, | |
boxedPlugins: [BoxPlugin<ComponentType, ListenerType, Context>] = [] | |
) { | |
self.component = component | |
self.listener = listener | |
self.boxedPlugins = boxedPlugins | |
} | |
final func addPlugin<P: Plugin>(_ plugin: P) where | |
P.ComponentType == ComponentType, | |
P.ListenerType == ListenerType, | |
P.Context == Context | |
{ | |
let boxedPlugin = BoxPlugin<ComponentType, ListenerType, Context>(plugin) | |
addPlugins([boxedPlugin]) | |
} | |
final func addPlugins(_ plugins: [BoxPlugin<ComponentType, ListenerType, Context>]) { | |
boxedPlugins += plugins.filter { self.boxedPlugins.contains($0) == false } | |
} | |
fileprivate final func createApplicableRIBs(_ context: Context) -> Observable<[String: ViewableRouting]> { | |
return Observable<BoxPlugin<ComponentType, ListenerType, Context>>.from(boxedPlugins) | |
.flatMap { item in | |
item.isApplicable(context) | |
.filter { $0 == true } | |
.map { _ in item } | |
.do( | |
onNext: { self.applicablePlugins[$0.killSwitchName] = $0 }, | |
onSubscribe: { self.applicablePlugins.removeAll() } | |
) | |
}.reduce([String: ViewableRouting]()) { buffer, next in | |
var buffer = buffer | |
buffer[next.killSwitchName] = next.createRIB(component: self.component, listener: self.listener) | |
return buffer | |
} | |
} | |
} | |
// MARK: - Plugin point | |
final class PluginPoint<ComponentType: Dependency, ListenerType: Interactable, Context>: | |
PluginsHolder<ComponentType, ListenerType, Context> { | |
typealias ApplicableContextProvider = ((Context) -> Void) -> Void | |
func applicableRIBs(_ contextProvider: @escaping ApplicableContextProvider) -> Observable<[String: ViewableRouting]> { | |
return Observable<Context>.fromAsync(contextProvider) | |
.flatMap { [createApplicableRIBs] in createApplicableRIBs($0) } | |
} | |
} | |
// MARK: - Automatic plugin point | |
final class AutomaticPluginPoint<ComponentType: Dependency, ListenerType: Interactable, Context>: | |
PluginsHolder<ComponentType, ListenerType, Context> { | |
typealias ApplicableContextProvider = ((Context) -> Void) -> Void | |
func engage( | |
with routing: Routing, | |
_ contextProvider: @escaping ApplicableContextProvider | |
) -> Observable<[String: ViewableRouting]> { | |
return Observable<Context>.fromAsync(contextProvider) | |
.flatMap { context in | |
Observable.zip( | |
routing.lifecycle.filter { $0 == .didLoad }, | |
self.createApplicableRIBs(context) | |
) | |
}.map { $0.1 } | |
.do(onNext: { $0.values.forEach(routing.attachChild) }) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment