Material Motion Exploring solutions that will empower creators with the tools needed to describe and implement rich, interactive motion on any platform. Edit this page · History
IndefiniteObservable MotionObservable
Status Interface level Implementation level Library
Stable as of December 4, 2016 L4: Runtime engineering L4: Runtime engineering indefinite-observable
UpdatesCurrent StatusInitiation dateCompletion date
Removed Subscription's automatic unsubscription on dealloc spec. Stable April 27, 2017
platformsrctests
Android View View
JavaScript View View
iOS (Swift) View View

IndefiniteObservable specification

This is the engineering specification for the IndefiniteObservable object.

IndefiniteObservable observers include a single channel of information called the next channel.

Observers can be extended to include other channels of information, if required by a given platform. This is most commonly necessary when a platform’s animation system is opaque to the application, such as Core Animation on iOS.

Overview

IndefiniteObservable is a minimal implementation of Observable with no concept of completion or failure.

Examples

public final class ValueObserver<T>: Observer<T> {
  public init(_ next: (T) -> Void) {
    self.next = next
  }

  public let next: (T) -> Void
}

public class ValueObservable<T>: IndefiniteObservable<ValueObserver<T>> {
  public final func subscribe(_ next: (T) -> Void) -> Subscription {
    return super.subscribe(observer: ValueObserver(next))
  }
}

let observable = ValueObservable<Int> { observer in
  observer.next(10)
  return noopDisconnect
}

observable.subscribe { value in
  print(value)
}

// Example operators:
extension ValueObservable {
  public func _operator<U>(_ operation: (ValueObserver<U>, T) -> Void) -> ValueObservable<U> {
    return ValueObservable<U> { observer in
      return self.subscribe(next: observer: ValueObserver<T> {
        operation(observer, $0)
      }).unsubscribe
    }
  }

  public func _map<W>(transform: (T) -> U) -> ValueObservable<W> {
    return _operator(op) { observer, value in
      observer.next(transform(value))
    }
  }

  public func _filter(predicate: (T) -> Bool) -> ValueObservable<T> {
    return _operator(op) { observer, value in
      if predicate(value) {
        observer.next(value)
      }
    }
  }
}

MVP

Expose a generic abstract Observer type

Define the base Observer type which has a single channel called next. next accepts an argument of type T.

public protocol Observer<T> {
  func next(value: T)
}

Expose a concrete IndefiniteObservable type

There is a single generic observer type: O. This is the type of observer that can be provided to the subscribe method. This type should conform to the abstract Observer type.

class IndefiniteObservable<O: Observer> {
}

Expose a Connect function type

Connect receives an observer and returns a Disconnect function.

public typealias Connect<O> = (O) -> Disconnect

Expose a noopDisconnect constant

This value can be returned by a connect function to indicate that no disconnection work will occur.

public let noopDisconnect: Disconnect = { }

Expose a Disconnect function type

The function signature expected to be returned by a Connect.

public typealias Disconnect = () -> Void

Expose an Unsubscribe function type

Unsubscribe should have the same shape as Disconnect.

public typealias Unsubscribe = () -> Void

Expose a Subscription object type

A representation of a subscription made by invoking subscribe on an IndefiniteObservable.

public final class Subscription {
  init(_ disconnect: () -> Void) {
    self.disconnect = disconnect
  }

  public func unsubscribe() {
    if disconnect != nil {
      disconnect()
      disconnect = nil
    }
  }

  private var disconnect: (Disconnect)?
}

This class should store a disconnect function. The first time unsubscribe is called, it should call the disconnect function. Thereafter, it should do nothing. disconnect shouldn’t be called more than once per connection.

Expose an IndefiniteObservable initializer

Requires a Connect type. Store connect as a private constant.

class IndefiniteObservable<O> {
  public init(connect: Connect<O>) {
    self._connect = connect
  }

  private let _connect: Connect<O>

Expose a subscribe API on IndefiniteObservable

Expose a subscribe API on IndefiniteObservable that accepts an observer and returns a Subscription.

subscribe should invoke self._connect with the provided observer.

class IndefiniteObservable<O> {
  func subscribe(observer: O) -> Subscription {
    return self._connect(observer);
  }
}