IndefiniteObservable
is a minimal implementation of Observable
with no concept of completion or failure.
- Swift 3
This library does not support Objective-C due to its heavy use of generics.
CocoaPods is a dependency manager for Objective-C and Swift libraries. CocoaPods automates the process of using third-party libraries in your projects. See the Getting Started guide for more information. You can install it with the following command:
gem install cocoapods
Add IndefiniteObservable
to your Podfile
:
pod 'IndefiniteObservable'
Then run the following command:
pod install
Create a Package.swift
file.
import PackageDescription
let package = Package(
name: "YourProject",
targets: [],
dependencies: [
.Package(url: "https://github.com/material-motion/indefinite-observable-swift.git”, majorVersion: 4)
]
)
swift build
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
- How to make an observable
- How to create a synchronous stream
- How to create an asynchronous stream using blocks
- How to subscribe to a stream
- How to unsubscribe from a stream
- How to create an synchronous stream using objects
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.