Files
swift-mirror/stdlib/public/Darwin/Foundation/Publishers+Timer.swift
2020-03-24 11:30:45 -07:00

329 lines
11 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
// Only support 64bit
#if !(os(iOS) && (arch(i386) || arch(arm)))
@_exported import Foundation // Clang module
import Combine
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension Timer {
/// Returns a publisher that repeatedly emits the current date on the given interval.
///
/// - Parameters:
/// - interval: The time interval on which to publish events. For example, a value of `0.5` publishes an event approximately every half-second.
/// - tolerance: The allowed timing variance when emitting events. Defaults to `nil`, which allows any variance.
/// - runLoop: The run loop on which the timer runs.
/// - mode: The run loop mode in which to run the timer.
/// - options: Scheduler options passed to the timer. Defaults to `nil`.
/// - Returns: A publisher that repeatedly emits the current date on the given interval.
public static func publish(
every interval: TimeInterval,
tolerance: TimeInterval? = nil,
on runLoop: RunLoop,
in mode: RunLoop.Mode,
options: RunLoop.SchedulerOptions? = nil)
-> TimerPublisher
{
return TimerPublisher(interval: interval, runLoop: runLoop, mode: mode, options: options)
}
/// A publisher that repeatedly emits the current date on a given interval.
public final class TimerPublisher: ConnectablePublisher {
public typealias Output = Date
public typealias Failure = Never
public let interval: TimeInterval
public let tolerance: TimeInterval?
public let runLoop: RunLoop
public let mode: RunLoop.Mode
public let options: RunLoop.SchedulerOptions?
private lazy var routingSubscription: RoutingSubscription = {
return RoutingSubscription(parent: self)
}()
// Stores if a `.connect()` happened before subscription, internally readable for tests
internal var isConnected: Bool {
return routingSubscription.isConnected
}
/// Creates a publisher that repeatedly emits the current date on the given interval.
///
/// - Parameters:
/// - interval: The interval on which to publish events.
/// - tolerance: The allowed timing variance when emitting events. Defaults to `nil`, which allows any variance.
/// - runLoop: The run loop on which the timer runs.
/// - mode: The run loop mode in which to run the timer.
/// - options: Scheduler options passed to the timer. Defaults to `nil`.
public init(interval: TimeInterval, tolerance: TimeInterval? = nil, runLoop: RunLoop, mode: RunLoop.Mode, options: RunLoop.SchedulerOptions? = nil) {
self.interval = interval
self.tolerance = tolerance
self.runLoop = runLoop
self.mode = mode
self.options = options
}
/// Adapter subscription to allow `Timer` to multiplex to multiple subscribers
/// the values produced by a single `TimerPublisher.Inner`
private class RoutingSubscription: Subscription, Subscriber, CustomStringConvertible, CustomReflectable, CustomPlaygroundDisplayConvertible {
typealias Input = Date
typealias Failure = Never
private typealias ErasedSubscriber = AnySubscriber<Output, Failure>
private let lock: Lock
// Inner is IUP due to init requirements
private var inner: Inner<RoutingSubscription>!
private var subscribers: [ErasedSubscriber] = []
private var _lockedIsConnected = false
var isConnected: Bool {
get {
lock.lock()
defer { lock.unlock() }
return _lockedIsConnected
}
set {
lock.lock()
let oldValue = _lockedIsConnected
_lockedIsConnected = newValue
// Inner will always be non-nil
let inner = self.inner!
lock.unlock()
guard newValue, !oldValue else {
return
}
inner.enqueue()
}
}
var description: String { return "Timer" }
var customMirror: Mirror { return inner.customMirror }
var playgroundDescription: Any { return description }
var combineIdentifier: CombineIdentifier { return inner.combineIdentifier }
init(parent: TimerPublisher) {
self.lock = Lock()
self.inner = Inner(parent, self)
}
deinit {
lock.cleanupLock()
}
func addSubsriber<S: Subscriber>(_ sub: S)
where
S.Failure == Failure,
S.Input == Output
{
lock.lock()
subscribers.append(AnySubscriber(sub))
lock.unlock()
sub.receive(subscription: self)
}
func receive(subscription: Subscription) {
lock.lock()
let subscribers = self.subscribers
lock.unlock()
for sub in subscribers {
sub.receive(subscription: subscription)
}
}
func receive(_ value: Input) -> Subscribers.Demand {
var resultingDemand: Subscribers.Demand = .max(0)
lock.lock()
let subscribers = self.subscribers
let isConnected = _lockedIsConnected
lock.unlock()
guard isConnected else { return .none }
for sub in subscribers {
resultingDemand += sub.receive(value)
}
return resultingDemand
}
func receive(completion: Subscribers.Completion<Failure>) {
lock.lock()
let subscribers = self.subscribers
lock.unlock()
for sub in subscribers {
sub.receive(completion: completion)
}
}
func request(_ demand: Subscribers.Demand) {
lock.lock()
// Inner will always be non-nil
let inner = self.inner!
lock.unlock()
inner.request(demand)
}
func cancel() {
lock.lock()
// Inner will always be non-nil
let inner = self.inner!
_lockedIsConnected = false
self.subscribers = []
lock.unlock()
inner.cancel()
}
}
public func receive<S: Subscriber>(subscriber: S) where Failure == S.Failure, Output == S.Input {
routingSubscription.addSubsriber(subscriber)
}
public func connect() -> Cancellable {
routingSubscription.isConnected = true
return routingSubscription
}
private typealias Parent = TimerPublisher
private final class Inner<Downstream: Subscriber>: NSObject, Subscription, CustomReflectable, CustomPlaygroundDisplayConvertible
where
Downstream.Input == Date,
Downstream.Failure == Never
{
private lazy var timer: Timer? = {
let t = Timer(
timeInterval: parent?.interval ?? 0,
target: self,
selector: #selector(timerFired),
userInfo: nil,
repeats: true
)
t.tolerance = parent?.tolerance ?? 0
return t
}()
private let lock: Lock
private var downstream: Downstream?
private var parent: Parent?
private var started: Bool
private var demand: Subscribers.Demand
override var description: String { return "Timer" }
var customMirror: Mirror {
lock.lock()
defer { lock.unlock() }
return Mirror(self, children: [
"downstream": downstream as Any,
"interval": parent?.interval as Any,
"tolerance": parent?.tolerance as Any
])
}
var playgroundDescription: Any { return description }
init(_ parent: Parent, _ downstream: Downstream) {
self.lock = Lock()
self.parent = parent
self.downstream = downstream
self.started = false
self.demand = .max(0)
super.init()
}
deinit {
lock.cleanupLock()
}
func enqueue() {
lock.lock()
guard let t = timer, let parent = self.parent, !started else {
lock.unlock()
return
}
started = true
lock.unlock()
parent.runLoop.add(t, forMode: parent.mode)
}
func cancel() {
lock.lock()
guard let t = timer else {
lock.unlock()
return
}
// clear out all optionals
downstream = nil
parent = nil
started = false
demand = .max(0)
timer = nil
lock.unlock()
// cancel the timer
t.invalidate()
}
func request(_ n: Subscribers.Demand) {
lock.lock()
defer { lock.unlock() }
guard parent != nil else {
return
}
demand += n
}
@objc
func timerFired(arg: Any) {
lock.lock()
guard let ds = downstream, parent != nil else {
lock.unlock()
return
}
// This publisher drops events on the floor when there is no space in the subscriber
guard demand > 0 else {
lock.unlock()
return
}
demand -= 1
lock.unlock()
let extra = ds.receive(Date())
guard extra > 0 else {
return
}
lock.lock()
demand += extra
lock.unlock()
}
}
}
}
#endif /* !(os(iOS) && (arch(i386) || arch(arm))) */