Created
July 20, 2019 18:42
-
-
Save davbeck/029cd67774b3fce613d2726a33e8df29 to your computer and use it in GitHub Desktop.
A Combine Publisher version of CADisplayLink.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Combine | |
import Foundation | |
#if canImport(QuartzCore) | |
import QuartzCore | |
#endif | |
struct DisplayLink: Publisher { | |
static var currentTime: TimeInterval { | |
#if canImport(QuartzCore) | |
return CACurrentMediaTime() | |
#else | |
return NSDate().timeIntervalSinceReferenceDate | |
#endif | |
} | |
class Subscription<S>: Combine.Subscription where S: Subscriber, Never == S.Failure, CFTimeInterval == S.Input { | |
#if os(iOS) | |
private lazy var link = CADisplayLink(target: self, selector: #selector(displayLinkFired)) | |
#else | |
private var timeInterval: TimeInterval | |
private var timer: Timer? { | |
didSet { | |
oldValue?.invalidate() | |
} | |
} | |
#endif | |
private let subscriber: AnySubscriber<CFTimeInterval, Never> | |
private var demand: Subscribers.Demand = .unlimited { | |
didSet { | |
#if os(iOS) | |
self.link.isPaused = self.demand == .none | |
#else | |
if self.demand == .none { | |
self.timer = nil | |
} else if self.timer == nil { | |
self.timer = Timer.scheduledTimer(timeInterval: self.timeInterval, target: self, selector: #selector(self.timerFired), userInfo: nil, repeats: true) | |
} | |
#endif | |
} | |
} | |
fileprivate init(subscriber: S, preferredFramesPerSecond: Int) { | |
self.subscriber = AnySubscriber(subscriber) | |
#if os(iOS) | |
self.link.preferredFramesPerSecond = preferredFramesPerSecond | |
self.link.add(to: .main, forMode: .default) | |
#else | |
self.timeInterval = preferredFramesPerSecond == 0 ? 1 / 60 : 1 / TimeInterval(preferredFramesPerSecond) | |
#endif | |
} | |
deinit { | |
#if os(iOS) | |
link.invalidate() | |
#else | |
timer?.invalidate() | |
#endif | |
} | |
func request(_ demand: Subscribers.Demand) { | |
self.demand = demand | |
} | |
func cancel() { | |
self.demand = .none | |
} | |
#if os(iOS) | |
@objc private func displayLinkFired(_: CADisplayLink) { | |
guard self.demand != .none else { return } | |
// this seems to always return .max(0) | |
_ = self.subscriber.receive(self.link.targetTimestamp) | |
} | |
#else | |
@objc private func timerFired() { | |
guard self.demand != .none else { return } | |
// this seems to always return .max(0) | |
_ = self.subscriber.receive(DisplayLink.currentTime) | |
} | |
#endif | |
} | |
public var preferredFramesPerSecond: Int | |
public init(preferredFramesPerSecond: Int = 0) { | |
self.preferredFramesPerSecond = preferredFramesPerSecond | |
} | |
public func receive<S>(subscriber: S) where S: Subscriber, Never == S.Failure, CFTimeInterval == S.Input { | |
let subscription = Subscription(subscriber: subscriber, preferredFramesPerSecond: self.preferredFramesPerSecond) | |
subscriber.receive(subscription: subscription) | |
} | |
public typealias Output = CFTimeInterval | |
public typealias Failure = Never | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment