Skip to content

Instantly share code, notes, and snippets.

@Alvazz
Forked from dehrom/Plugin.swift
Created November 3, 2020 20:10
Show Gist options
  • Save Alvazz/7a8154c61cc26488bb0cc64a94b5a5bc to your computer and use it in GitHub Desktop.
Save Alvazz/7a8154c61cc26488bb0cc64a94b5a5bc to your computer and use it in GitHub Desktop.
// Copyright © 2019 Ooma Inc. All rights reserved.
import Foundation
import RIBs
import RxSwift
// MARK: - Plugin
public protocol Plugin: AnyObject {
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 {
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 {
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 {
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 {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment