Skip to content

Instantly share code, notes, and snippets.

@dehrom
Last active August 30, 2022 01:12
Show Gist options
  • Select an option

  • Save dehrom/ac1a50cfbee3b573fd590150e652f914 to your computer and use it in GitHub Desktop.

Select an option

Save dehrom/ac1a50cfbee3b573fd590150e652f914 to your computer and use it in GitHub Desktop.

Revisions

  1. dehrom revised this gist Oct 7, 2019. 1 changed file with 77 additions and 45 deletions.
    122 changes: 77 additions & 45 deletions Plugin.swift
    Original file line number Diff line number Diff line change
    @@ -3,13 +3,10 @@
    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 {
    public protocol Plugin: AnyObject {
    associatedtype Component = Dependency
    associatedtype Listener = Interactable
    associatedtype Context
    @@ -30,42 +27,51 @@ public protocol Plugin: AnyObject, Equatable {
    }

    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)
    func ereaseToAnyPlugin() -> AnyPlugin {
    return .init(self)
    }
    }

    // MARK: - AnyPlugin

    public class AnyPlugin: Equatable, Hashable {
    public final 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 {
    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 = { concrete.isApplicable(with: configuration.context) }
    _build = { concrete.build(dependency: configuration.component, listener: configuration.listener) }

    _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() -> Observable<Bool> {
    return _isApplicable()
    func isApplicable(with context: Any) -> Observable<Bool> {
    return _isApplicable()(context)
    }

    func build() -> Routing {
    return _build()
    func build(dependency: Any, listener: Any) -> Routing {
    return _build()(dependency, listener)
    }

    private var _killswitchName: () -> String
    private var _isApplicable: () -> Observable<Bool>
    private let _build: () -> Routing
    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 {
    associatedtype Component: Dependency
    associatedtype Listener: Interactable
    associatedtype ApplicableContext

    func register(with configuration: PluginRegistration<Component, Listener, ApplicableContext>, registrationHandler: (AnyPlugin) -> Void)
    func registerPlugin(with registrationHandler: (AnyPlugin) -> Void)
    }

    // MARK: - PluginPoint

    public final class PluginPoint<Component, Listener, Context, Registrant: PluginRegistrable>
    where Registrant.Component == Component, Registrant.Listener == Listener, Registrant.ApplicableContext == Context {
    public final class PluginPoint<Component: Dependency, Listener: Interactable, Context, Registrant: PluginRegistrable> {
    init(
    routing: Routing,
    component: Component,
    listener: Listener,
    context: Context,
    context: @escaping () -> Context,
    registrant: Registrant
    ) {
    self.routing = routing
    pluginRegistration = (component, listener, context)
    self.component = component
    self.listener = listener
    self.context = context
    self.registrant = registrant
    }

    func engage() {
    registrant.register(with: pluginRegistration) { plugins.insert($0) }
    registrant.registerPlugin(with: { plugins.insert($0) })
    startPlugins()
    }

    private let pluginRegistration: PluginRegistration<Component, Listener, Context>
    private let registrant: Registrant
    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 { [createApplicableRIBs] _ in createApplicableRIBs() }
    .subscribe(
    onNext: { [routing] in $0.forEach { routing.attachChild($0) } },
    onError: { fatalError($0.localizedDescription) }
    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 createApplicableRIBs() -> Observable<[Routing]> {
    func buildApplicableRIBs() -> Observable<[Routing]> {
    let contextData = context()
    return Observable.from(
    plugins
    ).flatMap { (plugin: AnyPlugin) in
    plugin.isApplicable().filter { $0 == true }.map { _ in plugin.build() }
    ).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 {}
    }
  2. dehrom revised this gist Oct 4, 2019. 1 changed file with 99 additions and 110 deletions.
    209 changes: 99 additions & 110 deletions Plugin.swift
    Original file line number Diff line number Diff line change
    @@ -1,149 +1,138 @@
    //
    // Plugin.swift
    // HomeSecurity
    //
    // Created by Valery.Kokanov on 20/12/2018.
    // Copyright © 2018 Ooma Inc. All rights reserved.
    //
    // Copyright © 2019 Ooma Inc. All rights reserved.

    import Foundation
    import RIBs
    import RxSwift
    import Utils

    // 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
    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
    }

    extension Plugin {
    public static func == (lhs: Self, rhs: Self) -> Bool {
    return lhs.killSwitchName == rhs.killSwitchName
    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)
    }
    }

    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>
    // MARK: - AnyPlugin

    var killSwitchName: String {
    return _killSwitchName
    public class AnyPlugin: Equatable, Hashable {
    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
    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(_ context: Context) -> Observable<Bool> {
    return _isApplicable(context)
    func isApplicable() -> Observable<Bool> {
    return _isApplicable()
    }

    func createRIB(component: ComponentType, listener: ListenerType) -> ViewableRouting {
    return _createRIB(component, listener) as! ViewableRouting
    func build() -> Routing {
    return _build()
    }
    }

    // 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>]
    private var _killswitchName: () -> String
    private var _isApplicable: () -> Observable<Bool>
    private let _build: () -> Routing
    }

    final var pluginsNames: [String] {
    return boxedPlugins.map { $0.killSwitchName }
    public extension AnyPlugin {
    static func == (lhs: AnyPlugin, rhs: AnyPlugin) -> Bool {
    return lhs.killswitchName == rhs.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
    }
    func hash(into hasher: inout Hasher) {
    hasher.combine(killswitchName.hashValue)
    }
    }

    private(set) final var applicablePlugins: [String: BoxPlugin<ComponentType, ListenerType, Context>] = [:]
    // 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(
    component: ComponentType,
    listener: ListenerType,
    boxedPlugins: [BoxPlugin<ComponentType, ListenerType, Context>] = []
    routing: Routing,
    component: Component,
    listener: Listener,
    context: Context,
    registrant: Registrant
    ) {
    self.component = component
    self.listener = listener
    self.boxedPlugins = boxedPlugins
    self.routing = routing
    pluginRegistration = (component, listener, context)
    self.registrant = registrant
    }

    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])
    func engage() {
    registrant.register(with: pluginRegistration) { plugins.insert($0) }
    startPlugins()
    }

    final func addPlugins(_ plugins: [BoxPlugin<ComponentType, ListenerType, Context>]) {
    boxedPlugins += plugins.filter { self.boxedPlugins.contains($0) == false }
    }
    private let pluginRegistration: PluginRegistration<Component, Listener, Context>
    private let registrant: Registrant
    private let routing: Routing
    private let disposeBag = DisposeBag()

    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
    }
    }
    private(set) var plugins: Set<AnyPlugin> = .init()
    }

    // 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) }
    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)
    }
    }

    // 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) })
    func createApplicableRIBs() -> Observable<[Routing]> {
    return Observable.from(
    plugins
    ).flatMap { (plugin: AnyPlugin) in
    plugin.isApplicable().filter { $0 == true }.map { _ in plugin.build() }
    }.toArray()
    }
    }
  3. dehrom revised this gist Mar 23, 2019. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Plugin.swift
    Original file line number Diff line number Diff line change
    @@ -57,7 +57,7 @@ final class BoxPlugin<ComponentType: Dependency, ListenerType: Interactable, Con

    // MARK: - Plugins Holder

    class PluginsHolder<ComponentType: Dependency, ListenerType: Interactable, Context> {
    private class PluginsHolder<ComponentType: Dependency, ListenerType: Interactable, Context> {
    private let component: ComponentType
    private let listener: ListenerType
    private var boxedPlugins: [BoxPlugin<ComponentType, ListenerType, Context>]
  4. dehrom revised this gist Feb 25, 2019. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion Plugin.swift
    Original 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

    @discardableResult
    func engage(
    with routing: Routing,
    _ contextProvider: @escaping ApplicableContextProvider
  5. dehrom revised this gist Feb 25, 2019. 1 changed file with 23 additions and 26 deletions.
    49 changes: 23 additions & 26 deletions Plugin.swift
    Original 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
    }
    }
    final private(set) var applicablePlugins: [String: BoxPlugin<ComponentType, ListenerType, Context>] = [:]

    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>]) {
    applicablePlugins.removeAll()
    boxedPlugins += plugins.filter { self.boxedPlugins.contains($0) == false }
    }
    final fileprivate func createApplicableRIBs(_ context: Context) -> Observable<[String: ViewableRouting]> {

    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 })
    .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 { self.createApplicableRIBs($0) }
    .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]>
    {
    _ contextProvider: @escaping ApplicableContextProvider
    ) -> Observable<[String: ViewableRouting]> {
    return Observable<Context>.fromAsync(contextProvider)
    .flatMap { context in
    return Observable.zip(
    Observable.zip(
    routing.lifecycle.filter { $0 == .didLoad },
    self.createApplicableRIBs(context)
    )
    }.map { $0.1 }
    .do(onNext: { $0.values.forEach(routing.attachChild) })
    }

    }
  6. dehrom revised this gist Jan 5, 2019. 1 changed file with 13 additions and 2 deletions.
    15 changes: 13 additions & 2 deletions Plugin.swift
    Original 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> {

    func applicableRIBs(_ context: Context) -> Observable<[String: ViewableRouting]> {
    return createApplicableRIBs(context)
    typealias ApplicableContextProvider = ((Context) -> Void) -> Void

    func applicableRIBs(_ contextProvider: @escaping ApplicableContextProvider) -> Observable<[String: ViewableRouting]> {
    return Observable<Context>.fromAsync(contextProvider)
    .flatMap { self.createApplicableRIBs($0) }
    }

    }
  7. dehrom revised this gist Jan 5, 2019. 1 changed file with 0 additions and 2 deletions.
    2 changes: 0 additions & 2 deletions Plugin.swift
    Original 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

    private let disposeBag = DisposeBag()

    @discardableResult
    func engage(
    with routing: Routing,
  8. dehrom revised this gist Jan 5, 2019. 1 changed file with 103 additions and 31 deletions.
    134 changes: 103 additions & 31 deletions Plugin.swift
    Original file line number Diff line number Diff line change
    @@ -1,72 +1,144 @@
    import RIBs
    import RxSwift

    // MARK: - Plugin

    protocol Plugin: class {
    protocol Plugin: Equatable {
    associatedtype ListenerType: Interactable
    associatedtype ComponentType: Dependency
    associatedtype Context: Any
    associatedtype ReturnType = ViewableRouting
    var isApplicable: Bool { get }
    var killSwitchName: String { get }
    func isApplicable(_ context: Context) -> Observable<Bool>
    func createRIB(component: ComponentType, listener: ListenerType) -> ReturnType
    }

    class BoxPlugin<ComponentType: Dependency, ListenerType: Interactable>: Plugin {
    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 _isApplicable: Bool
    private let _killSwitchName: String
    private let _isApplicable: (Context) -> Observable<Bool>

    var isApplicable: Bool {
    return _isApplicable
    var killSwitchName: String {
    return _killSwitchName
    }

    init<P: Plugin>(_ plugin: P) where
    P.ListenerType == ListenerType,
    P.ComponentType == ComponentType
    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: - Factory

    extension RIBFactory {
    enum Error: Swift.Error {
    case failedBoxPluginCast
    case failedRoutingCast
    }
    }
    // MARK: - Plugins Holder

    final class RIBFactory<ComponentType: Dependency, ListenerType: Interactable> {
    class PluginsHolder<ComponentType: Dependency, ListenerType: Interactable, Context> {
    private let component: ComponentType
    private let listener: ListenerType
    private var boxedPlugins: [BoxPlugin<ComponentType, ListenerType, Context>]

    private var plugins: [String: Any] = [:]
    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) {
    init(
    component: ComponentType,
    listener: ListenerType,
    boxedPlugins: [BoxPlugin<ComponentType, ListenerType, Context>] = []
    ) {
    self.component = component
    self.listener = listener
    self.boxedPlugins = boxedPlugins
    }

    final func register<P: Plugin>(_ plugin: P, with key: String) where
    final func addPlugin<P: Plugin>(_ plugin: P) where
    P.ComponentType == ComponentType,
    P.ListenerType == ListenerType
    P.ListenerType == ListenerType,
    P.Context == Context
    {
    guard plugins.keys.contains(key) == false else { return }
    let boxedPlugin = BoxPlugin<ComponentType, ListenerType>(plugin)
    plugins[key] = boxedPlugin
    let boxedPlugin = BoxPlugin<ComponentType, ListenerType, Context>(plugin)
    addPlugins([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
    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) })
    }

    }
  9. dehrom created this gist Dec 26, 2018.
    72 changes: 72 additions & 0 deletions Plugin.swift
    Original 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
    }
    }