TestsTested | ✓ |
LangLanguage | SwiftSwift |
License | Apache 2 |
ReleasedLast Release | Apr 2017 |
SwiftSwift Version | 3.0 |
SPMSupports SPM | ✗ |
Maintained by Jeff Verkoeyen.
IndefiniteObservable
is a minimal implementation of Observable with no concept of completion or failure.
This library does not support Objective-C due to its heavy use of generics.
Import the framework:
@import IndefiniteObservable;
You will now have access to all of the APIs.
Check out a local copy of the repo to access the Catalog application by running the following commands:
git clone https://github.com/material-motion/indefinite-observable-swift.git
cd observable-swift
pod install
open IndefiniteObservable.xcworkspace
In this example we’ll make the simplest possible observable type: a value observable. We will use this concrete type in all of the following guides.
final class ValueObserver<T>: Observer {
typealias Value = T
init(_ next: @escaping (T) -> Void) {
self.next = next
}
let next: (T) -> Void
}
final class ValueObservable<T>: IndefiniteObservable<ValueObserver<T>> {
func subscribe(_ next: @escaping (T) -> Void) -> Subscription {
return super.subscribe(observer: ValueObserver(next))
}
}
let observable = ValueObservable<<#ValueType#>> { observer in
observer.next(<#value#>)
return noopDisconnect
}
If you have an API that provides a block-based mechanism for registering observers then you can create an asynchronous stream in place like so:
let observable = ValueObservable<<#ValueType#>> { observer in
let someToken = registerSomeCallback { callbackValue in
observer.next(callbackValue)
}
return {
unregisterCallback(someToken)
}
}
observable.subscribe { value in
print(value)
}
Unsubscribing will invoke the observable’s disconnect method. To unsubscribe, you must retain a reference to the subscription instance returned by subscribe.
let subscription = observable.subscribe { value in
print(value)
}
subscription.unsubscribe()
Many iOS/macOS APIs use delegation for event handling. To connect delegates with a stream you will need to create a Producer
class. A Producer
listens for events with an event delegate like didTap
and forwards those events to an IndefiniteObservable’s observer.
class DragConnection {
typealias Value = (state: UIGestureRecognizerState, location: CGPoint)
init(subscribedTo gesture: UIPanGestureRecognizer, observer: ValueObserver<Value>) {
self.gesture = gesture
self.observer = observer
gesture.addTarget(self, action: #selector(didPan))
// Populate the observer with the current gesture state.
observer.next(currentValue(for: gesture))
}
@objc func didPan(_ gesture: UIPanGestureRecognizer) {
observer.next(currentValue(for: gesture))
}
func currentValue(for gesture: UIPanGestureRecognizer) -> Value {
return (gesture.state, gesture.location(in: gesture.view!))
}
func disconnect() {
gesture?.removeTarget(self, action: #selector(didPan))
gesture = nil
}
var gesture: (UIPanGestureRecognizer)?
let observer: ValueObserver<Value>
}
let pan = UIPanGestureRecognizer()
view.addGestureRecognizer(pan)
let dragStream = ValueObservable<DragConnection.Value> { observer in
return DragConnection(subscribedTo: pan, observer: observer).disconnect
}
let subscription = dragStream.subscribe {
dump($0.state)
dump($0.location)
}
A Producer should be a type of Subscription.
class <#Name#>Producer: Subscription {
}
class DragConnection: Subscription {
typealias Value = (state: UIGestureRecognizerState, location: CGPoint)
}
Your initializer must accept and store an ValueObserver<Value>
instance.
init(subscribedTo gesture: UIPanGestureRecognizer, observer: ValueObserver<Value>) {
self.gesture = gesture
self.observer = observer
}
var gesture: (UIPanGestureRecognizer)?
let observer: ValueObserver<Value>
init(subscribedTo gesture: UIPanGestureRecognizer, observer: ValueObserver<Value>) {
...
gesture.addTarget(self, action: #selector(didPan))
}
@objc func didPan(_ gesture: UIPanGestureRecognizer) {
observer.next(currentValue(for: gesture))
}
func currentValue(for gesture: UIPanGestureRecognizer) -> Value {
return (gesture.state, gesture.location(in: gesture.view!))
}
You are responsible for disconnecting from and releasing any resources here.
func disconnect() {
gesture?.removeTarget(self, action: #selector(didPan))
gesture = nil
}
It often is helpful to provide the observer with the current state on registration.
init(subscribedTo gesture: UIPanGestureRecognizer, observer: ValueObserver<Value>) {
...
// Populate the observer with the current gesture state.
observer.next(currentValue(for: gesture))
}
let dragStream = ValueObservable<DragConnection.Value> { observer in
return DragConnection(subscribedTo: pan, observer: observer).disconnect
}
let subscription = dragStream.subscribe {
dump($0)
}
This library is meant to be a minimal implementation that never grows. As such, we only encourage contributions in the form of documentation, tests, and examples.
Learn more about our team, our community, and our contributor essentials.
Licensed under the Apache 2.0 license. See LICENSE for details.