mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
329 lines
11 KiB
Swift
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))) */
|