ReactiveKit

所属分类:collect
开发工具:Swift
文件大小:0KB
下载次数:0
上传日期:2023-03-24 13:56:18
上 传 者sh-1993
说明:  Swift React编程工具包,
(A Swift Reactive Programming Kit,)

文件列表:
.travis.yml (1166, 2023-03-24)
Assets/ (0, 2023-03-24)
Assets/logo.png (22403, 2023-03-24)
LICENSE (1079, 2023-03-24)
Package.swift (430, 2023-03-24)
Playground.playground/ (0, 2023-03-24)
Playground.playground/Pages/ (0, 2023-03-24)
Playground.playground/Pages/Creating Signals.xcplaygroundpage/ (0, 2023-03-24)
Playground.playground/Pages/Creating Signals.xcplaygroundpage/Contents.swift (1462, 2023-03-24)
Playground.playground/Pages/Exploration.xcplaygroundpage/ (0, 2023-03-24)
Playground.playground/Pages/Exploration.xcplaygroundpage/Contents.swift (299, 2023-03-24)
Playground.playground/Pages/Observing Signals.xcplaygroundpage/ (0, 2023-03-24)
Playground.playground/Pages/Observing Signals.xcplaygroundpage/Contents.swift (3114, 2023-03-24)
Playground.playground/Pages/Transforming Signals.xcplaygroundpage/ (0, 2023-03-24)
Playground.playground/Pages/Transforming Signals.xcplaygroundpage/Contents.swift (5875, 2023-03-24)
Playground.playground/Pages/Working with UI.xcplaygroundpage/ (0, 2023-03-24)
Playground.playground/Pages/Working with UI.xcplaygroundpage/Contents.swift (2126, 2023-03-24)
Playground.playground/Pages/Working with UI.xcplaygroundpage/timeline.xctimeline (120, 2023-03-24)
Playground.playground/contents.xcplayground (383, 2023-03-24)
ReactiveKit.podspec (831, 2023-03-24)
ReactiveKit.xcodeproj/ (0, 2023-03-24)
ReactiveKit.xcodeproj/project.pbxproj (82220, 2023-03-24)
ReactiveKit.xcodeproj/project.xcworkspace/ (0, 2023-03-24)
ReactiveKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata (156, 2023-03-24)
ReactiveKit.xcodeproj/project.xcworkspace/xcshareddata/ (0, 2023-03-24)
ReactiveKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (238, 2023-03-24)
ReactiveKit.xcodeproj/project.xcworkspace/xcshareddata/ReactiveKit.xcscmblueprint (3575, 2023-03-24)
ReactiveKit.xcodeproj/xcshareddata/ (0, 2023-03-24)
ReactiveKit.xcodeproj/xcshareddata/xcbaselines/ (0, 2023-03-24)
ReactiveKit.xcodeproj/xcshareddata/xcbaselines/ECBCCDD91BEB6B9B00723476.xcbaseline/ (0, 2023-03-24)
ReactiveKit.xcodeproj/xcshareddata/xcbaselines/ECBCCDD91BEB6B9B00723476.xcbaseline/15721443-CEB9-478C-919D-711F1A7CCA56.plist (621, 2023-03-24)
ReactiveKit.xcodeproj/xcshareddata/xcbaselines/ECBCCDD91BEB6B9B00723476.xcbaseline/FB4F14DE-1778-47BF-9AF1-A000D430FBBA.plist (551, 2023-03-24)
ReactiveKit.xcodeproj/xcshareddata/xcbaselines/ECBCCDD91BEB6B9B00723476.xcbaseline/Info.plist (1973, 2023-03-24)
ReactiveKit.xcodeproj/xcshareddata/xcschemes/ (0, 2023-03-24)
... ...

ReactiveKit [![Platform](https://img.shields.io/badge/platform-iOS%20%7C%20macOS%20%7C%20tvOS%20%7C%20watchOS%20%7C%20Linux-green.svg)](http://cocoadocs.org/docsets/ReactiveKit/) [![Build Status](https://github.com/DeclarativeHub/ReactiveKit/workflows/Build%20&%20Test/badge.svg)](https://github.com/DeclarativeHub/ReactiveKit/actions?query=workflow%3A%22Build+%26+Test%22) [![Twitter](https://img.shields.io/badge/twitter-@srdanrasic-red.svg?style=flat)](https://twitter.com/srdanrasic) __ReactiveKit__ is a lightweight Swift framework for reactive and functional reactive programming that enables you to get into the reactive world today. The framework is compatible with all Apple platforms and Linux. If you are developing an iOS or macOS app, make sure to also check out [Bond](https://github.com/DeclarativeHub/Bond) framework that provides UIKit and AppKit bindings, reactive delegates and data sources. ReactiveKit is currently in a process of API alignment with Apple's Combine framework. Types and functions are being renamed, where applicable, to match those of Combine. It's important to note that ReactiveKit will not become a drop-in replacement for Combine. The goal is to make interoperability and transition smooth. All work is being done in a backward compatible way and will be done gradually over a number of releases. Check out [release notes](https://github.com/DeclarativeHub/ReactiveKit/releases) to follow the process. This document will introduce the framework by going through its implementation. By the end you should be equipped with a pretty good understanding of how is the framework implemented and what are the best ways to use it. _To get started quickly, clone the project and explore available tutorials in the playgrounds of the workspace!_ ## Summary * [Introduction](#introduction) * [Signals](#signals) * [Wrapping asynchronous calls into signals](#wrapping-asynchronous-calls-into-signals) * [Disposing signals](#disposing-signals) * [Transforming signals](#transforming-signals) * [More about errors](#more-about-errors) * [Creating simple signals](#creating-simple-signals) * [Disposing in a bag](#disposing-in-a-bag) * [Threading](#threading) * [Bindings](#bindings) * [Binding targets](#binding-targets) * [Binding to a property](#binding-to-a-property) * [Sharing sequences of events](#sharing-sequences-of-events) * [Subjects](#subjects) * [Connectable signals](#connectable-signals) * [Implementing shareReplay operator](#implementing-sharereplay-operator) * [Handling signal errors](#handling-signal-errors) * [Generalized error handling](#generalized-error-handling) * [Tracking signal state](#tracking-signal-state) * [Single signal state tracking](#single-signal-state-tracking) * [Property](#property) * [Loading signals](#loading-signals) * [Consuming loading state](#consuming-loading-state) * [Transforming loading signals](#transforming-loading-signals) * [Loading property](#loading-property) * [Other common patterns](#other-common-patterns) * [Performing an action on .next event](#performing-an-action-on-next-event) * [Combining multiple signals](#combining-multiple-signals) * [Debugging](#debugging) * [Requirements](#requirements) * [Installation](#installation) * [Carthage](#carthage) * [CocoaPods](#cocoapods) * [Swift Package Manager](#swift-package-manager) * [Communication](#communication) * [Additional Documentation](#additional-documentation) * [License](#license) ## Introduction Consider how text of a text field changes as a user enters his name. Each entered letter gives us a new state. ``` ---[J]---[Ji]---[Jim]---> ``` We can think of these state changes as a sequence of events. It is quite similar to an array or a list, but with the difference that events are generated over time as opposed to having them all in memory at once. The idea behind reactive programming is that everything can be represented as a sequence. Let us consider another example - a network request. ``` ---[Response]---> ``` The outcome of a network request is a response. Although we have only one response, we can still think of it as a sequence. An array of one element is still an array. Arrays are finite so they have a property that we call size. It is a measure of how much memory the array occupies. When we talk about sequences over time, we do not know how many events they will generate during their lifetime. We do not know how many letters the user will enter. However, we would still like to know when the sequence is done generating the events. To get that information, we can introduce a special kind of event - a completion event. It is an event that marks the end of a sequence. No event shall follow the completion event. We will denote completion event visually with a vertical bar. ``` ---[J]---[Ji]---[Jim]---|---> ``` The completion event is important because it tells us that whatever was going on is now over. We can finalize the work at that point and dispose any resources that might have been used in processing the sequence. Unfortunately, the universe is not governed by order, rather by chaos. Unexpected things happen and we have to anticipate that. For example, a network request can fail so instead of a response, we can receive an error. ``` ---!Error!---> ``` In order to represent errors in our sequences, we will introduce yet another kind of event. We will call it a failure event. The failure event will be generated when something unexpected happens. Just like the completion event, the failure event will also represent the end of a sequence. No event shall follow the failure event. Let us see how the event is defined in ReactiveKit. ```swift extension Signal { /// An event of a sequence. public enum Event { /// An event that carries next element. case next(Element) /// An event that represents failure. Carries an error. case failed(Error) /// An event that marks the completion of a sequence. case completed } } ``` It is just an enumeration of the three kinds of events we have. Sequences will usually have zero or more `.next` events followed by either a `.completed` or a `.failed` event. What about sequences? In ReactiveKit they are called *signals*. Here is the protocol that defines them. ```swift /// Represents a sequence of events. public protocol SignalProtocol { /// The type of elements generated by the signal. associatedtype Element /// The type of error that can terminate the signal. associatedtype Error: Swift.Error /// Register the given observer. /// - Parameter observer: A function that will receive events. /// - Returns: A disposable that can be used to cancel the observation. public func observe(with observer: @escaping Observer) -> Disposable } ``` A signal represents the sequence of events. The most important thing you can do on the sequence is observe the events it generates. Events are received by the *observer*. An observer is nothing more than a function that accepts an event. ```swift /// Represents a type that receives events. public typealias Observer = (Signal.Event) -> Void ``` ## Signals We have seen the protocol that defines signals, but what about the implementation? Let us implement a basic signal type! ```swift public struct Signal: SignalProtocol { private let producer: (Observer) -> Void public init(producer: @escaping (Observer) -> Void) { self.producer = producer } public func observe(with observer: @escaping Observer) { producer(observer) } } ``` We have defined our signal as a struct of one property - a producer. As you can see, producer is just a function that takes the observer as an argument. When we start observing the signal, what we do is basically execute the producer with the given observer. That is how simple signals are! > Signal in ReactiveKit is implemented almost like what we have shown here. It has few additions that give us some guarantees that we will talk about later. Let us create an instance of the signal that first sends three positive integers to the observer and then completes. Visually that would look like: ``` ---[1]---[2]---[3]---|---> ``` While in the code, we would do: ```swift let counter = Signal { observer in // send first three positive integers observer(.next(1)) observer(.next(2)) observer(.next(3)) // send completed event observer(.completed) } ``` Since the observer is just a function that receives events, we just execute it with the event whenever we want to send a new one. We always finalize the sequence by sending either `.completed` or `.failed` event so that the receiver knows when the signal is over with event production. ReactiveKit wraps the observer into a struct with various helper methods to make it easier to send events. Here is a protocol that defines it. ```swift /// Represents a type that receives events. public protocol ObserverProtocol { /// Type of elements being received. associatedtype Element /// Type of error that can be received. associatedtype Error: Swift.Error /// Send the event to the observer. func on(_ event: Signal.Event) } ``` Our observer we introduced earlier is basically the `on(_:)` method. ReactiveKit also provides this extension on the observer: ```swift public extension ObserverProtocol { /// Convenience method to send `.next` event. public func receive(_ element: Element) { on(.next(element)) } /// Convenience method to send `.failed` or `.completed` event. public func receive(completion: Subscribers.Completion) { switch completion { case .finished: on(.completed) case .failure(let error): on(.failed(error)) } } /// Convenience method to send `.next` event followed by a `.completed` event. public func receive(lastElement element: Element) { receive(element) receive(completion: .finished) } } ``` So with ReactiveKit we can implement the previous example like this: ```swift let counter = Signal { observer in // send first three positive integers observer.receive(1) observer.receive(2) observer.receive(3) // send completed event observer.receive(completion: .finished) } ``` What happens when we observe such a signal? Remember, the observer is a function that receives events so we can just pass a closure to our observe method. ```swift counter.observe(with: { event in print(event) }) ``` Of course, we will get our three events printed out. ```swift next(1) next(2) next(3) completed ``` ### Wrapping asynchronous calls into signals We can easily wrap asynchronous calls into signals because of the way we implemented our `Signal` type. Let us say that we have an asynchronous function that fetches the user. ```swift func getUser(completion: (Result) -> Void) -> URLSessionTask ``` The function communicates fetch result through a completion closure and a `Result` type whose instance will contain either a user or an error. To wrap this into a signal, all we need to do is call that function within our signal initializer's producer closure and send relevant events as they happen. ```swift func getUser() -> Signal { return Signal { observer in getUser(completion: { result in switch result { case .success(let user): observer.receive(user) observer.receive(completion: .finished) case .failure(let error): observer.receive(completion: .failure(error)) }) // return disposable, continue reading } } ``` If we now observe this signal, we will get either a user and a completion event ``` ---[User]---|---> ``` or an error ``` ---!ClientError!---> ``` In code, getting the user would look like: ```swift let user = getUser() user.observe { event in print(event) // prints ".next(user), .completed" in case of successful response } ``` Let me ask you one important question here. When is the request to get the user executed, i.e. when is the asynchronous function `getUser(completion:)` called? Think about it. We call `getUser(completion:)` within our producer closure that we pass to the signal initializer. That closure however is not executed when the signal is created. That means that the code `let user = getUser()` does not trigger the request. It merely creates a signal that knows how to execute the request. Request is made when we call the `observe(with:)` method because that is the point when our producer closure gets executed. It also means that if we call the `observe(with:)` method more than once, we will call the producer more than once, so we will execute the request more than once. This is a very powerful aspect of signals and we will get back to it later when we will talk about [sharing sequences of events](#sharing-sequences-of-events). For now just remember that each call to `observe(with:)` means that events get produced all over again. ### Disposing signals Our example function `getUser(completion:)` returns a `URLSessionTask` object. We often do not think about it, but HTTP requests can be cancelled. When the screen gets dismissed, we should probably cancel any ongoing requests. A way to do that is to call `cancel()` on `URLSessionTask` that we used to make the request. How do we handle that with signals? If you have been reading the code examples carefully, you have probably noticed that we did not correctly conform our `Signal` to `SignalProtocol`. The protocol specifies that the `observe(with:)` method returns something called `Disposable`. A disposable is an object that can cancel the signal observation and any underlying tasks. Let me give you the definition of a disposable from ReactiveKit. ```swift public protocol Disposable { /// Cancel the signal observation and any underlying tasks. func dispose() /// Returns `true` if already disposed. var isDisposed: Bool { get } } ``` It has a method to cancel the observation and a property that can tell us if it has been disposed or not. Cancelling the observation is also referred to as *disposing the signal*. There are various implementations of `Disposable`, but let us focus on the one that is most commonly used in signal creation. When the signal gets disposed, we often want to perform some action to clean up the resources or stop underlying tasks. What a better way to do that then to execute a closure when the the signal gets disposed. Let us implement a disposable that executes a given closure when it gets disposed. We will call it `BlockDisposable`. ```swift public final class BlockDisposable: Disposable { private var handler: (() -> Void)? public var isDisposed: Bool { return handler == nil } public init(_ handler: @escaping () -> Void) { self.handler = handler } public func dispose() { handler?() handler = nil } } ``` Simple enough. It just executes the given closure when the `dispose()` method is called. How do we use such a disposable? Well, we will need to improve our signal implementation. Who should create the disposable? Since the disposable represents a way to communicate the signal cancellation, it is obviously the one who created the signal that should also provide a disposable that can cancel the signal. To do that we will refactor the signal producer to return a disposable. Additionally, we will return that disposable from the `observe(with:)` method so that whoever will be observing the signal can cancel the observation. ```swift public struct Signal: SignalProtocol { private let producer: (Observer) -> Disposable public init(producer: @escaping (Observer) -> Disposable) { self.producer = producer } public func observe(with observer: @escaping Observer) -> Disposable { return producer(observer) } } ``` This means that when we are creating a signal, we also have to provide a disposable. Let us refactor our asynchronous function wrapper signal to provide a disposable. ```swift func getUser() -> Signal { return Signal { observer in let task = getUser(completion: { result in switch result { case .success(let user): observer.receive(user) observer.receive(completion: .finished) case .failure(let error): observer.receive(completion: .failure(error)) }) return BlockDisposable { task.cancel() } } } ``` We just return an instance of `BlockDisposable` that cancels the task when it gets disposed. We can then get that disposable when observing the signal. ```swift let disposable = getUser().observe { event in print(event) } ``` When we are no longer interested in signal events, we can just dispose the disposable. It will cancel the observation and cancel network task. ```swift disposable.dispose() ``` > For the actual implementation of `Signal` in ReactiveKit there are additional mechanisms that prevent events from being sent when the signal is disposed so there is a guarantee that no events will be received after the signal is disposed. Any events sent from the producer after the signal is disposed are ignored. > In ReactiveKit, signals are automatically disposed when they terminate with either a `.completed` or `.failed` event. ### Transforming signals This is all so good, but why should we do it? What are the benefits? Here comes the most interesting aspect of reactive programming - signal operators. Operators are functions (i.e. methods) that transform one or more signals into other signals. One of the basic operations on signals is filtering. Say that we have a signal of city names, but we want only the names starting with letter *P*. ``` filter( ---[Berlin]---[Paris]---[London]---[Porto]---|---> ) --------------[Paris]--------------[Porto]---|---> ``` How could we implement such an operator? Very easily. ```swift extension SignalProtocol { /// Emit only elements that pass `isIncluded` test. public func filter(_ isIncluded: @escaping (Element) -> Bool) -> Signal { return Signal { observer in return self.observe { event in switch event { case .next(let element): if isIncluded(element) { observer.receive(element) } default: observer(event) } } } } } ``` We have written an extension method on the `SignalProtocol` in which we create a signal. In the created signal's producer we observe *self* - the signal we are filtering - and propagate `.next` events that pass the test. We also propagate completion and failure events in the `default` case. We use the operator by calling it on a signal. ```swift cities.filter { $0.hasPrefix("P") }.observe { event in print(event) // prints .next("Paris"), .next("Porto"), .completed } ``` There are many operators on signals. ReactiveKit is basically a collection of signal operators. Let us see another common one. When observing signals we often do not care about terminal events, all we care about is the elements in `.next` events. We could write an operator that gives us just that. ```s ... ...

近期下载者

相关文件


收藏者