-
Notifications
You must be signed in to change notification settings - Fork 10.2k
/
Schedulers+DispatchQueue.swift
283 lines (231 loc) · 10.8 KB
/
Schedulers+DispatchQueue.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
//===----------------------------------------------------------------------===//
//
// 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)))
import Combine
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
private func clampedIntProduct(_ m1: Int, _ m2: UInt64) -> Int {
assert(m2 > 0, "multiplier must be positive")
guard m1 < Int.max, m2 < Int.max else { return Int.max }
let (result, overflow) = m1.multipliedReportingOverflow(by: Int(m2))
if overflow {
return m1 > 0 ? Int.max : Int.min
}
return result
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension DispatchTimeInterval {
fileprivate var nanoseconds: Int {
switch self {
case .seconds(let s): return clampedIntProduct(s, NSEC_PER_SEC)
case .milliseconds(let ms): return clampedIntProduct(ms, NSEC_PER_MSEC)
case .microseconds(let us): return clampedIntProduct(us, NSEC_PER_USEC)
case .nanoseconds(let ns): return ns
case .never: return Int.max
}
}
}
// This is Strideable except: <rdar://problem/35158274>
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension DispatchTime /* : Strideable */ {
typealias Stride = DispatchTimeInterval
public func distance(to other: DispatchTime) -> DispatchTimeInterval {
let lhs = other.rawValue
let rhs = rawValue
if lhs >= rhs {
return DispatchTimeInterval.nanoseconds(Int(lhs - rhs))
} else {
return DispatchTimeInterval.nanoseconds(0 - Int(rhs - lhs))
}
}
public func advanced(by n: DispatchTimeInterval) -> DispatchTime {
return self + n
}
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension DispatchQueue: Scheduler {
/// The scheduler time type used by the dispatch queue.
public struct SchedulerTimeType: Strideable, Codable, Hashable {
/// The dispatch time represented by this type.
public var dispatchTime: DispatchTime
/// Creates a dispatch queue time type instance.
///
/// - Parameter time: The dispatch time to represent.
public init(_ time: DispatchTime) {
dispatchTime = time
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let time = DispatchTime(uptimeNanoseconds: try container.decode(UInt64.self))
self.init(time)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(dispatchTime.uptimeNanoseconds)
}
/// Returns the distance to another dispatch queue time.
///
/// - Parameter other: Another dispatch queue time.
/// - Returns: The time interval between this time and the provided time.
public func distance(to other: SchedulerTimeType) -> Stride {
return Stride(self.dispatchTime.distance(to: other.dispatchTime))
}
/// Returns a dispatch queue scheduler time calculated by advancing this instance’s time by the given interval.
///
/// - Parameter n: A time interval to advance.
/// - Returns: A dispatch queue time advanced by the given interval from this instance’s time.
public func advanced(by n: Stride) -> SchedulerTimeType {
return SchedulerTimeType(self.dispatchTime.advanced(by: n.timeInterval))
}
public func hash(into hasher: inout Hasher) {
hasher.combine(dispatchTime.rawValue)
}
public struct Stride: SchedulerTimeIntervalConvertible, Comparable, SignedNumeric, ExpressibleByFloatLiteral, Hashable, Codable {
/// If created via floating point literal, the value is converted to nanoseconds via multiplication.
public typealias FloatLiteralType = Double
/// Nanoseconds, same as DispatchTimeInterval.
public typealias IntegerLiteralType = Int
public typealias Magnitude = Int
/// The value of this time interval in nanoseconds.
public var magnitude: Int
/// A `DispatchTimeInterval` created with the value of this type in nanoseconds.
public var timeInterval: DispatchTimeInterval {
return .nanoseconds(magnitude)
}
/// Creates a dispatch queue time interval from the given dispatch time interval.
///
/// - Parameter timeInterval: A dispatch time interval.
public init(_ timeInterval: DispatchTimeInterval) {
magnitude = Int(timeInterval.nanoseconds)
}
/// Creates a dispatch queue time interval from a floating-point seconds value.
///
/// - Parameter value: The number of seconds, as a `Double`.
public init(floatLiteral value: Double) {
magnitude = Int(value * 1_000_000_000)
}
/// Creates a dispatch queue time interval from an integer seconds value.
///
/// - Parameter value: The number of seconds, as an `Int`.
public init(integerLiteral value: Int) {
magnitude = value * 1_000_000_000
}
/// Creates a dispatch queue time interval from a binary integer type.
///
/// If `exactly` cannot convert to an `Int`, the resulting time interval is `nil`.
/// - Parameter exactly: A binary integer representing a time interval.
public init?<T>(exactly source: T) where T: BinaryInteger {
if let v = Int(exactly: source) {
magnitude = v
} else {
return nil
}
}
// ---
public static func < (lhs: Stride, rhs: Stride) -> Bool {
return lhs.magnitude < rhs.magnitude
}
// ---
public static func * (lhs: Stride, rhs: Stride) -> Stride {
return Stride(.nanoseconds(lhs.magnitude * rhs.magnitude))
}
public static func + (lhs: Stride, rhs: Stride) -> Stride {
return Stride(.nanoseconds(lhs.magnitude + rhs.magnitude))
}
public static func - (lhs: Stride, rhs: Stride) -> Stride {
return Stride(.nanoseconds(lhs.magnitude - rhs.magnitude))
}
// ---
public static func -= (lhs: inout Stride, rhs: Stride) {
let result = lhs - rhs
lhs = result
}
public static func *= (lhs: inout Stride, rhs: Stride) {
let result = lhs * rhs
lhs = result
}
public static func += (lhs: inout Stride, rhs: Stride) {
let result = lhs + rhs
lhs = result
}
// ---
public static func seconds(_ s: Double) -> Stride {
return Stride(.nanoseconds(Int(s * 1_000_000_000)))
}
public static func seconds(_ s: Int) -> Stride {
return Stride(.seconds(s))
}
public static func milliseconds(_ ms: Int) -> Stride {
return Stride(.milliseconds(ms))
}
public static func microseconds(_ us: Int) -> Stride {
return Stride(.microseconds(us))
}
public static func nanoseconds(_ ns: Int) -> Stride {
return Stride(.nanoseconds(ns))
}
}
}
/// Options that affect the operation of the dispatch queue scheduler.
public struct SchedulerOptions {
/// The dispatch queue quality of service.
public var qos: DispatchQoS
/// The dispatch queue work item flags.
public var flags: DispatchWorkItemFlags
/// The dispatch group, if any, that should be used for performing actions.
public var group: DispatchGroup?
public init(qos: DispatchQoS = .unspecified, flags: DispatchWorkItemFlags = [], group: DispatchGroup? = nil) {
self.qos = qos
self.flags = flags
self.group = group
}
}
public var minimumTolerance: SchedulerTimeType.Stride {
return SchedulerTimeType.Stride(DispatchTimeInterval.seconds(0))
}
public var now: DispatchQueue.SchedulerTimeType {
return SchedulerTimeType(DispatchTime.now())
}
public func schedule(options: SchedulerOptions?, _ action: @escaping () -> Void) {
let qos = options?.qos ?? .unspecified
let flags = options?.flags ?? []
if let group = options?.group {
// Distinguish on the group because it appears to not be a call-through like the others. This may need to be adjusted.
self.async(group: group, qos: qos, flags: flags, execute: action)
} else {
self.async(qos: qos, flags: flags, execute: action)
}
}
public func schedule(after date: SchedulerTimeType,
tolerance: SchedulerTimeType.Stride,
options: SchedulerOptions?,
_ action: @escaping () -> Void) {
// TODO: Tolerance ignored
let qos = options?.qos ?? .unspecified
let flags = options?.flags ?? []
self.asyncAfter(deadline: date.dispatchTime, qos: qos, flags: flags, execute: action)
}
public func schedule(after date: SchedulerTimeType,
interval: SchedulerTimeType.Stride,
tolerance: SchedulerTimeType.Stride,
options: SchedulerOptions?,
_ action: @escaping () -> Void) -> Cancellable {
let source = DispatchSource.makeTimerSource(flags: DispatchSource.TimerFlags(), queue: self)
source.schedule(deadline: date.dispatchTime,
repeating: interval.timeInterval,
leeway: tolerance.timeInterval)
source.setEventHandler(handler: action)
source.resume()
return AnyCancellable(source.cancel)
}
}
#endif /* !(os(iOS) && (arch(i386) || arch(arm))) */