Last active
August 30, 2022 01:12
-
-
Save dehrom/ac1a50cfbee3b573fd590150e652f914 to your computer and use it in GitHub Desktop.
Revisions
-
dehrom revised this gist
Oct 7, 2019 . 1 changed file with 77 additions and 45 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -3,13 +3,10 @@ import Foundation import RIBs import RxSwift // MARK: - Plugin public protocol Plugin: AnyObject { associatedtype Component = Dependency associatedtype Listener = Interactable associatedtype Context @@ -30,42 +27,51 @@ public protocol Plugin: AnyObject, Equatable { } public extension Plugin { func ereaseToAnyPlugin() -> AnyPlugin { return .init(self) } } // MARK: - AnyPlugin public final class AnyPlugin: Equatable, Hashable { var killswitchName: String { return _killswitchName() } init<P>(_ concrete: P) where P: Plugin { func cast<T, V>(_ obj: T, toType: V.Type) -> V { guard let value = obj as? V else { fatalError("Couldn't cast \(T.self) to type \(V.self), check plugin: \(P.self)") } return value } _killswitchName = { concrete.killswitchName } _isApplicable = { { let context = cast($0, toType: P.Context.self) return concrete.isApplicable(with: context) } } _build = { { let component = cast($0, toType: P.Component.self) let listner = cast($1, toType: P.Listener.self) return concrete.build(dependency: component, listener: listner) } } } func isApplicable(with context: Any) -> Observable<Bool> { return _isApplicable()(context) } func build(dependency: Any, listener: Any) -> Routing { return _build()(dependency, listener) } private let _killswitchName: () -> String private let _isApplicable: () -> (Any) -> Observable<Bool> private let _build: () -> (Any, Any) -> Routing } public extension AnyPlugin { @@ -81,58 +87,84 @@ public extension AnyPlugin { // MARK: - Registration public protocol PluginRegistrable: AnyObject { func registerPlugin(with registrationHandler: (AnyPlugin) -> Void) } // MARK: - PluginPoint public final class PluginPoint<Component: Dependency, Listener: Interactable, Context, Registrant: PluginRegistrable> { init( routing: Routing, component: Component, listener: Listener, context: @escaping () -> Context, registrant: Registrant ) { self.routing = routing self.component = component self.listener = listener self.context = context self.registrant = registrant } func engage() { registrant.registerPlugin(with: { plugins.insert($0) }) startPlugins() } func check() { registrant.registerPlugin(with: { plugins.insert($0) }) plugins.forEach { _ = $0.build(dependency: component, listener: listener) } plugins.removeAll(keepingCapacity: true) } private let routing: Routing private let component: Component private let listener: Listener private let context: () -> Context private let registrant: Registrant private let disposeBag = DisposeBag() private(set) var plugins: Set<AnyPlugin> = .init() } private extension PluginPoint { func startPlugins() { routing.lifecycle.filter { $0 == .didLoad }.flatMap { [buildApplicableRIBs] _ in buildApplicableRIBs() }.subscribe( onNext: { [routing] in $0.forEach { routing.attachChild($0) } }, onError: { fatalError($0.localizedDescription) } ).disposed(by: disposeBag) } func buildApplicableRIBs() -> Observable<[Routing]> { let contextData = context() return Observable.from( plugins ).flatMap { [component, listener] (plugin: AnyPlugin) in plugin.isApplicable(with: contextData).filter { $0 == true }.map { _ in plugin.build(dependency: component, listener: listener) } }.toArray() } } // MARK: - Test public class Test { public init() { let object = Object() let builder = ParentBuilder(dependency: object) routing = builder.build(withListener: object) } public func start() { routing.interactable.activate() routing.load() } private let routing: ParentRouting private class Object: ParentDependency, ParentListener {} } -
dehrom revised this gist
Oct 4, 2019 . 1 changed file with 99 additions and 110 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -1,149 +1,138 @@ // Copyright © 2019 Ooma Inc. All rights reserved. import Foundation import RIBs import RxSwift import Utils // MARK: - Plugin public typealias PluginRegistration<Component, Listener, Context> = (component: Component, listener: Listener, context: Context) public protocol Plugin: AnyObject, Equatable { associatedtype Component = Dependency associatedtype Listener = Interactable associatedtype Context /// **Unique** plugin name var killswitchName: String { get } /// Check the ability to create a RIB by this plugin. /// /// - Returns: Observable with checking result /// - Note: Checking process may take some time. func isApplicable(with context: Context) -> Observable<Bool> /// Build RIB with provided dependecies. /// /// - Returns: Routing for target RIB. func build(dependency: Component, listener: Listener) -> Routing } public extension Plugin { static func == (lhs: Self, rhs: Self) -> Bool { return lhs.killswitchName == rhs.killswitchName } func ereaseToAnyPlugin(with configuration: PluginRegistration<Component, Listener, Context>) -> AnyPlugin { return .init(concrete: self, configuration: configuration) } } // MARK: - AnyPlugin public class AnyPlugin: Equatable, Hashable { var killswitchName: String { return _killswitchName() } init<P, Component, Listener, Context>( concrete: P, configuration: PluginRegistration<Component, Listener, Context> ) where P: Plugin, Component == P.Component, Listener == P.Listener, Context == P.Context { _killswitchName = { concrete.killswitchName } _isApplicable = { concrete.isApplicable(with: configuration.context) } _build = { concrete.build(dependency: configuration.component, listener: configuration.listener) } } func isApplicable() -> Observable<Bool> { return _isApplicable() } func build() -> Routing { return _build() } private var _killswitchName: () -> String private var _isApplicable: () -> Observable<Bool> private let _build: () -> Routing } public extension AnyPlugin { static func == (lhs: AnyPlugin, rhs: AnyPlugin) -> Bool { return lhs.killswitchName == rhs.killswitchName } func hash(into hasher: inout Hasher) { hasher.combine(killswitchName.hashValue) } } // MARK: - Registration public protocol PluginRegistrable: AnyObject { associatedtype Component: Dependency associatedtype Listener: Interactable associatedtype ApplicableContext func register(with configuration: PluginRegistration<Component, Listener, ApplicableContext>, registrationHandler: (AnyPlugin) -> Void) } // MARK: - PluginPoint public final class PluginPoint<Component, Listener, Context, Registrant: PluginRegistrable> where Registrant.Component == Component, Registrant.Listener == Listener, Registrant.ApplicableContext == Context { init( routing: Routing, component: Component, listener: Listener, context: Context, registrant: Registrant ) { self.routing = routing pluginRegistration = (component, listener, context) self.registrant = registrant } func engage() { registrant.register(with: pluginRegistration) { plugins.insert($0) } startPlugins() } private let pluginRegistration: PluginRegistration<Component, Listener, Context> private let registrant: Registrant private let routing: Routing private let disposeBag = DisposeBag() private(set) var plugins: Set<AnyPlugin> = .init() } private extension PluginPoint { func startPlugins() { routing.lifecycle .filter { $0 == .didLoad } .flatMap { [createApplicableRIBs] _ in createApplicableRIBs() } .subscribe( onNext: { [routing] in $0.forEach { routing.attachChild($0) } }, onError: { fatalError($0.localizedDescription) } ).disposed(by: disposeBag) } func createApplicableRIBs() -> Observable<[Routing]> { return Observable.from( plugins ).flatMap { (plugin: AnyPlugin) in plugin.isApplicable().filter { $0 == true }.map { _ in plugin.build() } }.toArray() } } -
dehrom revised this gist
Mar 23, 2019 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -57,7 +57,7 @@ final class BoxPlugin<ComponentType: Dependency, ListenerType: Interactable, Con // 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>] -
dehrom revised this gist
Feb 25, 2019 . 1 changed file with 0 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -133,7 +133,6 @@ final class AutomaticPluginPoint<ComponentType: Dependency, ListenerType: Intera PluginsHolder<ComponentType, ListenerType, Context> { typealias ApplicableContextProvider = ((Context) -> Void) -> Void func engage( with routing: Routing, _ contextProvider: @escaping ApplicableContextProvider -
dehrom revised this gist
Feb 25, 2019 . 1 changed file with 23 additions and 26 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -31,11 +31,11 @@ final class BoxPlugin<ComponentType: Dependency, ListenerType: Interactable, Con 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, @@ -45,11 +45,11 @@ final class BoxPlugin<ComponentType: Dependency, ListenerType: Interactable, Con _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 } @@ -61,19 +61,19 @@ class PluginsHolder<ComponentType: Dependency, ListenerType: Interactable, Conte 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, @@ -83,7 +83,7 @@ class PluginsHolder<ComponentType: Dependency, ListenerType: Interactable, Conte self.listener = listener self.boxedPlugins = boxedPlugins } final func addPlugin<P: Plugin>(_ plugin: P) where P.ComponentType == ComponentType, P.ListenerType == ListenerType, @@ -92,19 +92,21 @@ class PluginsHolder<ComponentType: Dependency, ListenerType: Interactable, Conte 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) @@ -113,41 +115,36 @@ class PluginsHolder<ComponentType: Dependency, ListenerType: Interactable, Conte } } // 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 @discardableResult 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) }) } } -
dehrom revised this gist
Jan 5, 2019 . 1 changed file with 13 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,11 @@ // // Plugin.swift // HomeSecurity // // Created by Valery.Kokanov on 20/12/2018. // Copyright © 2018 Ooma Inc. All rights reserved. // import RIBs import RxSwift @@ -111,8 +119,11 @@ class PluginsHolder<ComponentType: Dependency, ListenerType: Interactable, Conte 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 { self.createApplicableRIBs($0) } } } -
dehrom revised this gist
Jan 5, 2019 . 1 changed file with 0 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -124,8 +124,6 @@ final class AutomaticPluginPoint<ComponentType: Dependency, ListenerType: Intera typealias ApplicableContextProvider = ((Context) -> Void) -> Void @discardableResult func engage( with routing: Routing, -
dehrom revised this gist
Jan 5, 2019 . 1 changed file with 103 additions and 31 deletions.There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -1,72 +1,144 @@ 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 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 } } final private(set) 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>]) { applicablePlugins.removeAll() boxedPlugins += plugins.filter { self.boxedPlugins.contains($0) == false } } final fileprivate 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 }) }.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> { func applicableRIBs(_ context: Context) -> Observable<[String: ViewableRouting]> { return createApplicableRIBs(context) } } // MARK: - Automatic plugin point final class AutomaticPluginPoint<ComponentType: Dependency, ListenerType: Interactable, Context>: PluginsHolder<ComponentType, ListenerType, Context> { typealias ApplicableContextProvider = ((Context) -> Void) -> Void private let disposeBag = DisposeBag() @discardableResult func engage( with routing: Routing, _ contextProvider: @escaping ApplicableContextProvider) -> Observable<[String: ViewableRouting]> { return Observable<Context>.fromAsync(contextProvider) .flatMap { context in return Observable.zip( routing.lifecycle.filter { $0 == .didLoad }, self.createApplicableRIBs(context) ) }.map { $0.1 } .do(onNext: { $0.values.forEach(routing.attachChild) }) } } -
dehrom created this gist
Dec 26, 2018 .There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,72 @@ import RIBs // MARK: - Plugin protocol Plugin: class { associatedtype ListenerType: Interactable associatedtype ComponentType: Dependency associatedtype ReturnType = ViewableRouting var isApplicable: Bool { get } func createRIB(component: ComponentType, listener: ListenerType) -> ReturnType } class BoxPlugin<ComponentType: Dependency, ListenerType: Interactable>: Plugin { private let _createRIB: (ComponentType, ListenerType) -> Any private let _isApplicable: Bool var isApplicable: Bool { return _isApplicable } init<P: Plugin>(_ plugin: P) where P.ListenerType == ListenerType, P.ComponentType == ComponentType { _createRIB = plugin.createRIB _isApplicable = plugin.isApplicable } func createRIB(component: ComponentType, listener: ListenerType) -> ViewableRouting { return _createRIB(component, listener) as! ViewableRouting } } // MARK: - Factory extension RIBFactory { enum Error: Swift.Error { case failedBoxPluginCast case failedRoutingCast } } final class RIBFactory<ComponentType: Dependency, ListenerType: Interactable> { private let component: ComponentType private let listener: ListenerType private var plugins: [String: Any] = [:] init(component: ComponentType, listener: ListenerType) { self.component = component self.listener = listener } final func register<P: Plugin>(_ plugin: P, with key: String) where P.ComponentType == ComponentType, P.ListenerType == ListenerType { guard plugins.keys.contains(key) == false else { return } let boxedPlugin = BoxPlugin<ComponentType, ListenerType>(plugin) plugins[key] = boxedPlugin } final func createRIB<ReturnType>(key: String) throws -> ReturnType { guard let plugin = plugins[key] as? BoxPlugin<ComponentType, ListenerType> else { throw Error.failedBoxPluginCast } guard let rib = plugin.createRIB(component: component, listener: listener) as? ReturnType else { throw Error.failedRoutingCast } return rib } }