Skip to content

Instantly share code, notes, and snippets.

@srea
Forked from dehrom/Plugin.swift
Created May 30, 2019 02:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save srea/83727c71d5e5292c6d99c490e133b2e9 to your computer and use it in GitHub Desktop.
Save srea/83727c71d5e5292c6d99c490e133b2e9 to your computer and use it in GitHub Desktop.
//
// 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