swift/Diffuser/Sources/Diffuser/fuser/Fuser.swift (94 lines of code) (raw):

// Copyright (c) 2019 Spotify AB. // // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. import Foundation /// A `Fuser<A>` is a stream of events of type `A`. /// It supports: /// - `connect`: start listening to events from the Fuser. A callback function is supplied to connect. /// - `dispose`: stop listening to events. Can be called on objects returned by `connect`. /// - `extract`: apply a function to every event emitted by a Fuser. public class Fuser<A> { public typealias Disposable = _Disposable private let lock = NSRecursiveLock() private let source: AnySource<A>; public init(source: AnySource<A>) { self.source = source } public init(_ children: [Fuser<A>]) { self.source = AnySource { effect in let disposables = children.map { $0.connect(effect) } return AnonymousDisposable { // TODO review thread safety of this operation disposables.forEach { $0.dispose() } } } } public convenience init(_ children: Fuser<A>...) { self.init(children) } /// Start observing the events of a `Fuser`. Remember to call `.dispose()` on the disposable returned when /// connecting. Otherwise you may leak resources. /// /// - Parameter effect: the side-effect which should be performed when the `Fuser` emits an event /// - Returns: a disposable which can be called to unsubscribe from the `Fuser`'s events public func connect(_ effect: @escaping Effect<A>) -> Disposable { var isDisposed = false let safeEffect: Effect<A> = { value in self.lock.synchronized { if !isDisposed { effect(value) } } } let disposable = source.connect(safeEffect) return AnonymousDisposable { self.lock.synchronized { isDisposed = true } disposable.dispose() } } } public extension Fuser { /// Create a `Fuser` when given a `Source`. static func from<S: Source>(_ source: S) -> Fuser<A> where S.A == A { return Fuser(source: AnySource(source)); } /// See `from(Source)` /// This is a shorthand for creating a `Fuser` from a closure directly. static func from(_ source: @escaping (@escaping Effect<A>) -> Disposable) -> Fuser<A> { return from(AnySource(source)) } /// `fromAll` merges a list of `Fuser`s. /// Connecting to this returned `Fuser` will internally connect to each of the merged `Fuser`s, and calling `dispose` /// will disconnect from all of these connections. static func fromAll(_ children: [Fuser<A>]) -> Fuser<A> { return Fuser(children) } /// Vararg variant of `fromAll([Fuser<..>])` static func fromAll(_ children: Fuser<A>...) -> Fuser<A> { return Fuser(children) } /// `extract` takes a function which it applies to each event emitted by a `Fuser`. /// /// We intentionally do not provide other combinators than `extract`, like `flatMap`, `filter`, or `reduce`. The `Fuser` /// is designed for aggregating UI-events and should be placed in the UI-layer of an application. `extract` is primarily /// intended for converting from UIKit types to types from your domain. Any additional interpretation of events should /// be placed outside of the `Fuser` and outside the UI-layer. /// /// - Parameters: /// - transformation: the function to be applied to each event emitted by the `fuser` parameter /// - fuser: the fuser to which the `transformation` function should be applied static func extract<B>( _ transformation: @escaping (B) -> A, _ fuser: Fuser<B> ) -> Fuser<A> { return Fuser<A>(source: AnySource { effect in return fuser.connect { b in let a = transformation(b) effect(a) } }) } /// Extract a constant from each event emitted by a `Fuser`. /// /// - Parameters: /// - constant: the constant that should be emitted everytime the `fuser` parameter emits an event /// - fuser: the fuser to which the `transformation` function should be applied static func extractConstant<B>( _ constant: A, _ fuser: Fuser<B> ) -> Fuser<A> { return Fuser<A>(source: AnySource { effect in return fuser.connect { b in effect(constant) } }) } /// `extractUnlessNil` takes a function which it applies to each event emitted by a `Fuser`. The event is dropped if the function /// returns `nil`. /// /// - Parameters: /// - transformation: the function to be applied to each event emitted by the `fuser` parameter. The event will be ignored if /// this function returns `nil` /// - fuser: the fuser to which the `transformation` function should be applied static func extractUnlessNil<B>( _ transformation: @escaping (B) -> A?, _ fuser: Fuser<B> ) -> Fuser<A> { return Fuser<A>(source: AnySource { effect in return fuser.connect { b in if let a = transformation(b) { effect(a) } } }) } } private extension NSRecursiveLock { @discardableResult func synchronized<R>(closure: () -> R) -> R { lock() defer { self.unlock() } return closure() } }