redesign task's api
This commit is contained in:
parent
a88a95ce13
commit
a3fdd633a6
|
@ -7,3 +7,4 @@ disabled_rules:
|
|||
- file_length
|
||||
- function_body_length
|
||||
- identifier_name
|
||||
- type_name
|
|
@ -37,7 +37,6 @@
|
|||
OBJ_59 /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_19 /* Task.swift */; };
|
||||
OBJ_60 /* TaskCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_20 /* TaskCenter.swift */; };
|
||||
OBJ_61 /* Time.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_21 /* Time.swift */; };
|
||||
OBJ_62 /* Timeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_22 /* Timeline.swift */; };
|
||||
OBJ_63 /* Weekday.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_23 /* Weekday.swift */; };
|
||||
OBJ_70 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; };
|
||||
OBJ_81 /* AtomicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_26 /* AtomicTests.swift */; };
|
||||
|
@ -86,7 +85,6 @@
|
|||
OBJ_19 /* Task.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = "<group>"; };
|
||||
OBJ_20 /* TaskCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskCenter.swift; sourceTree = "<group>"; };
|
||||
OBJ_21 /* Time.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Time.swift; sourceTree = "<group>"; };
|
||||
OBJ_22 /* Timeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timeline.swift; sourceTree = "<group>"; };
|
||||
OBJ_23 /* Weekday.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weekday.swift; sourceTree = "<group>"; };
|
||||
OBJ_26 /* AtomicTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_27 /* BagTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BagTests.swift; sourceTree = "<group>"; };
|
||||
|
@ -205,7 +203,6 @@
|
|||
OBJ_19 /* Task.swift */,
|
||||
OBJ_20 /* TaskCenter.swift */,
|
||||
OBJ_21 /* Time.swift */,
|
||||
OBJ_22 /* Timeline.swift */,
|
||||
OBJ_23 /* Weekday.swift */,
|
||||
);
|
||||
name = Schedule;
|
||||
|
@ -309,7 +306,6 @@
|
|||
OBJ_59 /* Task.swift in Sources */,
|
||||
OBJ_60 /* TaskCenter.swift in Sources */,
|
||||
OBJ_61 /* Time.swift in Sources */,
|
||||
OBJ_62 /* Timeline.swift in Sources */,
|
||||
OBJ_63 /* Weekday.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
|
@ -28,9 +28,7 @@ struct BagKeyGenerator: Sequence, IteratorProtocol {
|
|||
|
||||
/// Advances to the next key and returns it, or nil if no next key exists.
|
||||
mutating func next() -> BagKey? {
|
||||
if k.i == UInt64.max {
|
||||
return nil
|
||||
}
|
||||
if k.i == UInt64.max { return nil }
|
||||
defer { k = BagKey(underlying: k.i + 1) }
|
||||
return k
|
||||
}
|
||||
|
|
|
@ -331,7 +331,7 @@ extension Date {
|
|||
|
||||
extension DispatchSourceTimer {
|
||||
|
||||
/// Schedule this timer later.
|
||||
/// Schedule this timer after the given interval.
|
||||
func schedule(after timeout: Interval) {
|
||||
if timeout.isNegative { return }
|
||||
let ns = timeout.nanoseconds.clampedToInt()
|
||||
|
|
|
@ -29,7 +29,7 @@ public enum Monthday {
|
|||
|
||||
/// Returns a dateComponenets of this monthday, using gregorian calender and
|
||||
/// current time zone.
|
||||
public func asDateComponents() -> DateComponents {
|
||||
public func asDateComponents(_ timeZone: TimeZone = .current) -> DateComponents {
|
||||
var month, day: Int
|
||||
switch self {
|
||||
case .january(let n): month = 1; day = n
|
||||
|
@ -47,7 +47,7 @@ public enum Monthday {
|
|||
}
|
||||
return DateComponents(
|
||||
calendar: Calendar.gregorian,
|
||||
timeZone: TimeZone.current,
|
||||
timeZone: timeZone,
|
||||
month: month,
|
||||
day: day)
|
||||
}
|
||||
|
@ -56,8 +56,8 @@ public enum Monthday {
|
|||
extension Date {
|
||||
|
||||
/// Returns a Boolean value indicating whether this date is the monthday in current time zone..
|
||||
public func `is`(_ monthday: Monthday) -> Bool {
|
||||
let components = monthday.asDateComponents()
|
||||
public func `is`(_ monthday: Monthday, in timeZone: TimeZone = .current) -> Bool {
|
||||
let components = monthday.asDateComponents(timeZone)
|
||||
|
||||
let m = Calendar.gregorian.component(.month, from: self)
|
||||
let d = Calendar.gregorian.component(.day, from: self)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Foundation
|
||||
|
||||
/// Type used to represents a date-based amount of time in the ISO-8601 calendar system,
|
||||
/// Type used to represent a date-based amount of time in the ISO-8601 calendar system,
|
||||
/// such as '2 years, 3 months and 4 days'.
|
||||
///
|
||||
/// It's a little different from `Interval`:
|
||||
|
@ -65,11 +65,19 @@ public struct Period {
|
|||
for (word, number) in Period.quantifiers.read({ $0 }) {
|
||||
str = str.replacingOccurrences(of: word, with: "\(number)")
|
||||
}
|
||||
|
||||
// swiftlint:disable force_try
|
||||
let regexp = try! NSRegularExpression(pattern: "( and |, )")
|
||||
str = regexp.stringByReplacingMatches(in: str, range: NSRange(location: 0, length: str.count), withTemplate: "$")
|
||||
|
||||
let mark: Character = "秋"
|
||||
str = regexp.stringByReplacingMatches(
|
||||
in: str,
|
||||
range: NSRange(location: 0, length: str.count),
|
||||
withTemplate: String(mark)
|
||||
)
|
||||
|
||||
var period = 0.year
|
||||
for pair in str.split(separator: "$").map({ $0.split(separator: " ") }) {
|
||||
for pair in str.split(separator: mark).map({ $0.split(separator: " ") }) {
|
||||
guard
|
||||
pair.count == 2,
|
||||
let number = Int(pair[0])
|
||||
|
@ -171,10 +179,18 @@ public struct Period {
|
|||
|
||||
/// Returns a dateComponenets of this period, using gregorian calender and
|
||||
/// current time zone.
|
||||
public func asDateComponents() -> DateComponents {
|
||||
return DateComponents(year: years, month: months, day: days,
|
||||
hour: hours, minute: minutes, second: seconds,
|
||||
nanosecond: nanoseconds)
|
||||
public func asDateComponents(_ timeZone: TimeZone = .current) -> DateComponents {
|
||||
return DateComponents(
|
||||
calendar: Calendar.gregorian,
|
||||
timeZone: timeZone,
|
||||
year: years,
|
||||
month: months,
|
||||
day: days,
|
||||
hour: hours,
|
||||
minute: minutes,
|
||||
second: seconds,
|
||||
nanosecond: nanoseconds
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,50 +1,54 @@
|
|||
import Foundation
|
||||
|
||||
/// `Plan` represents a plan that gives time at which a task should be
|
||||
/// `Plan` represents a sequence of times at which a task should be
|
||||
/// executed.
|
||||
///
|
||||
/// `Plan` is `Interval` based.
|
||||
public struct Plan {
|
||||
public struct Plan: Sequence {
|
||||
|
||||
private var iSeq: AnySequence<Interval>
|
||||
private var seq: AnySequence<Interval>
|
||||
|
||||
private init<S>(_ sequence: S) where S: Sequence, S.Element == Interval {
|
||||
iSeq = AnySequence(sequence)
|
||||
seq = AnySequence(sequence)
|
||||
}
|
||||
|
||||
func makeIterator() -> AnyIterator<Interval> {
|
||||
return iSeq.makeIterator()
|
||||
/// Returns an iterator over the interval of this sequence.
|
||||
public func makeIterator() -> AnyIterator<Interval> {
|
||||
return seq.makeIterator()
|
||||
}
|
||||
|
||||
/// Schedules a task with this plan.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - queue: The queue to which the task will be dispatched.
|
||||
/// - onElapse: The action to do when time is out.
|
||||
/// - queue: The dispatch queue to which the block should be dispatched.
|
||||
/// - block: A block to be executed when time is up.
|
||||
/// - Returns: The task just created.
|
||||
public func `do`(queue: DispatchQueue,
|
||||
onElapse: @escaping (Task) -> Void) -> Task {
|
||||
return Task(plan: self, queue: queue, onElapse: onElapse)
|
||||
public func `do`(
|
||||
queue: DispatchQueue,
|
||||
block: @escaping (Task) -> Void
|
||||
) -> Task {
|
||||
return Task(plan: self, queue: queue, block: block)
|
||||
}
|
||||
|
||||
/// Schedules a task with this plan.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - queue: The queue to which the task will be dispatched.
|
||||
/// - onElapse: The action to do when time is out.
|
||||
/// - queue: The dispatch queue to which the block should be dispatched.
|
||||
/// - block: A block to be executed when time is up.
|
||||
/// - Returns: The task just created.
|
||||
public func `do`(queue: DispatchQueue,
|
||||
onElapse: @escaping () -> Void) -> Task {
|
||||
return self.do(queue: queue, onElapse: { (_) in onElapse() })
|
||||
public func `do`(
|
||||
queue: DispatchQueue,
|
||||
block: @escaping () -> Void
|
||||
) -> Task {
|
||||
return self.do(queue: queue, block: { (_) in block() })
|
||||
}
|
||||
}
|
||||
|
||||
extension Plan {
|
||||
|
||||
/// Creates a plan from a `makeUnderlyingIterator()` method.
|
||||
/// Creates a plan whose `makeIterator()` method forwards to makeUnderlyingIterator.
|
||||
///
|
||||
/// The task will be executed after each interval produced by the iterator
|
||||
/// that `makeUnderlyingIterator` returns.
|
||||
/// The task will be executed after each interval.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
|
@ -52,11 +56,11 @@ extension Plan {
|
|||
/// var i = 0
|
||||
/// return AnyIterator {
|
||||
/// i += 1
|
||||
/// return i
|
||||
/// return i // 1, 2, 3, ...
|
||||
/// }
|
||||
/// }
|
||||
/// plan.do {
|
||||
/// print(Date())
|
||||
/// logTimestamp()
|
||||
/// }
|
||||
///
|
||||
/// > "2001-01-01 00:00:00"
|
||||
|
@ -71,15 +75,15 @@ extension Plan {
|
|||
}
|
||||
|
||||
/// Creates a plan from a list of intervals.
|
||||
///
|
||||
/// The task will be executed after each interval in the array.
|
||||
/// - Note: Returns `Plan.never` if given no parameters.
|
||||
public static func of(_ intervals: Interval...) -> Plan {
|
||||
return Plan.of(intervals)
|
||||
}
|
||||
|
||||
/// Creates a plan from a list of intervals.
|
||||
///
|
||||
/// The task will be executed after each interval in the array.
|
||||
/// - Note: Returns `Plan.never` if given an empty array.
|
||||
public static func of<S>(_ intervals: S) -> Plan where S: Sequence, S.Element == Interval {
|
||||
return Plan(intervals)
|
||||
}
|
||||
|
@ -87,10 +91,9 @@ extension Plan {
|
|||
|
||||
extension Plan {
|
||||
|
||||
/// Creates a plan from a `makeUnderlyingIterator()` method.
|
||||
/// Creates a plan whose `makeIterator()` method forwards to makeUnderlyingIterator.
|
||||
///
|
||||
/// The task will be executed at each date
|
||||
/// produced by the iterator that `makeUnderlyingIterator` returns.
|
||||
/// The task will be executed at each date.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
|
@ -99,42 +102,44 @@ extension Plan {
|
|||
/// return Date().addingTimeInterval(3)
|
||||
/// }
|
||||
/// }
|
||||
/// print("now:", Date())
|
||||
///
|
||||
/// plan.do {
|
||||
/// print("task", Date())
|
||||
/// logTimestamp()
|
||||
/// }
|
||||
///
|
||||
/// > "now: 2001-01-01 00:00:00"
|
||||
/// > "task: 2001-01-01 00:00:03"
|
||||
/// > "2001-01-01 00:00:00"
|
||||
/// > "2001-01-01 00:00:03"
|
||||
/// > "2001-01-01 00:00:06"
|
||||
/// > "2001-01-01 00:00:09"
|
||||
/// ...
|
||||
///
|
||||
/// You are not supposed to return `Date()` in making interator.
|
||||
/// If you want to execute a task immediately,
|
||||
/// use `Plan.now` then `concat` another plan instead.
|
||||
/// You should not return `Date()` in making iterator.
|
||||
/// If you want to execute a task immediately, use `Plan.now`.
|
||||
public static func make<I>(
|
||||
_ makeUnderlyingIterator: @escaping () -> I
|
||||
) -> Plan where I: IteratorProtocol, I.Element == Date {
|
||||
return Plan.make { () -> AnyIterator<Interval> in
|
||||
var iterator = makeUnderlyingIterator()
|
||||
var last: Date!
|
||||
var prev: Date!
|
||||
return AnyIterator {
|
||||
last = last ?? Date()
|
||||
prev = prev ?? Date()
|
||||
guard let next = iterator.next() else { return nil }
|
||||
defer { last = next }
|
||||
return next.interval(since: last)
|
||||
defer { prev = next }
|
||||
return next.interval(since: prev)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a plan from a list of dates.
|
||||
///
|
||||
/// The task will be executed at each date in the array.
|
||||
public static func of(_ dates: Date...) -> Plan {
|
||||
return Plan.of(dates)
|
||||
}
|
||||
|
||||
/// Creates a plan from a list of dates.
|
||||
///
|
||||
/// The task will be executed at each date in the array.
|
||||
/// - Note: Returns `Plan.never` if given no parameters.
|
||||
public static func of<S>(_ sequence: S) -> Plan where S: Sequence, S.Element == Date {
|
||||
return Plan.make(sequence.makeIterator)
|
||||
}
|
||||
|
@ -143,13 +148,13 @@ extension Plan {
|
|||
public var dates: AnySequence<Date> {
|
||||
return AnySequence { () -> AnyIterator<Date> in
|
||||
let iterator = self.makeIterator()
|
||||
var last: Date!
|
||||
var prev: Date!
|
||||
return AnyIterator {
|
||||
last = last ?? Date()
|
||||
prev = prev ?? Date()
|
||||
guard let interval = iterator.next() else { return nil }
|
||||
// swiftlint:disable shorthand_operator
|
||||
last = last + interval
|
||||
return last
|
||||
prev = prev + interval
|
||||
return prev
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -157,27 +162,27 @@ extension Plan {
|
|||
|
||||
extension Plan {
|
||||
|
||||
/// A plan with a distant past date.
|
||||
/// A plan of a distant past date.
|
||||
public static var distantPast: Plan {
|
||||
return Plan.of(Date.distantPast)
|
||||
}
|
||||
|
||||
/// A plan with a distant future date.
|
||||
/// A plan of a distant future date.
|
||||
public static var distantFuture: Plan {
|
||||
return Plan.of(Date.distantFuture)
|
||||
}
|
||||
|
||||
/// A plan that is never going to happen.
|
||||
/// A plan that will never happen.
|
||||
public static var never: Plan {
|
||||
return Plan.make {
|
||||
AnyIterator<Date> { nil }
|
||||
AnyIterator<Interval> { nil }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Plan {
|
||||
|
||||
/// Returns a new plan by concatenating a plan to this plan.
|
||||
/// Returns a new plan by concatenating the given plan to this plan.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
|
@ -198,40 +203,44 @@ extension Plan {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a new plan by merging a plan to this plan.
|
||||
/// Returns a new plan by merging the given plan to this plan.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// let s0 = Plan.of(1.second, 3.seconds, 5.seconds)
|
||||
/// let s1 = Plan.of(2.seconds, 4.seconds, 6.seconds)
|
||||
/// let s2 = s0.concat(s1)
|
||||
/// let s2 = s0.merge(s1)
|
||||
/// > s2
|
||||
/// > 1.second, 1.seconds, 2.seconds, 2.seconds, 3.seconds, 3.seconds
|
||||
public func merge(_ plan: Plan) -> Plan {
|
||||
return Plan.make { () -> AnyIterator<Date> in
|
||||
let i0 = self.dates.makeIterator()
|
||||
let i1 = plan.dates.makeIterator()
|
||||
var buffer0: Date!
|
||||
var buffer1: Date!
|
||||
|
||||
var buf0: Date!
|
||||
var buf1: Date!
|
||||
|
||||
return AnyIterator<Date> {
|
||||
if buffer0 == nil { buffer0 = i0.next() }
|
||||
if buffer1 == nil { buffer1 = i1.next() }
|
||||
if buf0 == nil { buf0 = i0.next() }
|
||||
if buf1 == nil { buf1 = i1.next() }
|
||||
|
||||
var d: Date!
|
||||
if let d0 = buffer0, let d1 = buffer1 {
|
||||
if let d0 = buf0, let d1 = buf1 {
|
||||
d = Swift.min(d0, d1)
|
||||
} else {
|
||||
d = buffer0 ?? buffer1
|
||||
d = buf0 ?? buf1
|
||||
}
|
||||
|
||||
if d == buffer0 { buffer0 = nil }
|
||||
if d == buffer1 { buffer1 = nil }
|
||||
if d == nil { return d }
|
||||
|
||||
if d == buf0 { buf0 = nil; return d }
|
||||
if d == buf1 { buf1 = nil }
|
||||
return d
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new plan by only taking the first specific number of this plan.
|
||||
/// Returns a new plan by taking the first specific number of intervals from this plan.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
|
@ -251,7 +260,7 @@ extension Plan {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a new plan by only taking the part before the date.
|
||||
/// Returns a new plan by taking the part before the given date.
|
||||
public func until(_ date: Date) -> Plan {
|
||||
return Plan.make { () -> AnyIterator<Date> in
|
||||
let iterator = self.dates.makeIterator()
|
||||
|
@ -263,58 +272,57 @@ extension Plan {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Plan {
|
||||
|
||||
/// Creates a plan that executes the task immediately.
|
||||
public static var now: Plan {
|
||||
return Plan.of(0.nanosecond)
|
||||
}
|
||||
|
||||
/// Creates a plan that executes the task after delay.
|
||||
/// Creates a plan that executes the task after the given interval.
|
||||
public static func after(_ delay: Interval) -> Plan {
|
||||
return Plan.of(delay)
|
||||
}
|
||||
|
||||
/// Creates a plan that executes the task every interval.
|
||||
/// Creates a plan that executes the task after the given interval then repeat the execution.
|
||||
public static func after(_ delay: Interval, repeating interval: Interval) -> Plan {
|
||||
return Plan.after(delay).concat(Plan.every(interval))
|
||||
}
|
||||
|
||||
/// Creates a plan that executes the task at the given date.
|
||||
public static func at(_ date: Date) -> Plan {
|
||||
return Plan.of(date)
|
||||
}
|
||||
|
||||
/// Creates a plan that executes the task every given interval.
|
||||
public static func every(_ interval: Interval) -> Plan {
|
||||
return Plan.make {
|
||||
AnyIterator { interval }
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a plan that executes the task after delay then repeat
|
||||
/// every interval.
|
||||
public static func after(_ delay: Interval, repeating interval: Interval) -> Plan {
|
||||
return Plan.after(delay).concat(Plan.every(interval))
|
||||
}
|
||||
|
||||
/// Creates a plan that executes the task at the specific date.
|
||||
public static func at(_ date: Date) -> Plan {
|
||||
return Plan.of(date)
|
||||
}
|
||||
|
||||
/// Creates a plan that executes the task every period.
|
||||
/// Creates a plan that executes the task every given period.
|
||||
public static func every(_ period: Period) -> Plan {
|
||||
return Plan.make { () -> AnyIterator<Interval> in
|
||||
let calendar = Calendar.gregorian
|
||||
var last: Date!
|
||||
var prev: Date!
|
||||
return AnyIterator {
|
||||
last = last ?? Date()
|
||||
guard let next = calendar.date(byAdding: period.asDateComponents(),
|
||||
to: last) else {
|
||||
prev = prev ?? Date()
|
||||
guard
|
||||
let next = calendar.date(
|
||||
byAdding: period.asDateComponents(),
|
||||
to: prev)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
defer { last = next }
|
||||
return next.interval(since: last)
|
||||
defer { prev = next }
|
||||
return next.interval(since: prev)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a plan that executes the task every period.
|
||||
///
|
||||
/// See Period's constructor
|
||||
/// See Period's constructor: `init?(_ string: String)`.
|
||||
public static func every(_ period: String) -> Plan {
|
||||
guard let p = Period(period) else {
|
||||
return Plan.never
|
||||
|
@ -326,16 +334,16 @@ extension Plan {
|
|||
extension Plan {
|
||||
|
||||
/// `DateMiddleware` represents a middleware that wraps a plan
|
||||
/// which was only specified date without time.
|
||||
/// which was only specified with date without time.
|
||||
///
|
||||
/// You should call `at` method to get the plan with time specified.
|
||||
/// You should call `at` method to specified time of the plan.
|
||||
public struct DateMiddleware {
|
||||
|
||||
fileprivate let plan: Plan
|
||||
|
||||
/// Returns a plan at the specific time.
|
||||
/// Creates a plan with time specified.
|
||||
public func at(_ time: Time) -> Plan {
|
||||
guard !self.plan.isNever() else { return .never }
|
||||
if plan.isNever() { return .never }
|
||||
|
||||
var interval = time.intervalSinceStartOfDay
|
||||
return Plan.make { () -> AnyIterator<Interval> in
|
||||
|
@ -350,56 +358,54 @@ extension Plan {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a plan at the specific time.
|
||||
/// Creates a plan with time specified.
|
||||
///
|
||||
/// See Time's constructor
|
||||
/// See Time's constructor: `init?(_ string: String)`.
|
||||
public func at(_ time: String) -> Plan {
|
||||
guard
|
||||
!self.plan.isNever(),
|
||||
let time = Time(time)
|
||||
else {
|
||||
if plan.isNever() { return .never }
|
||||
guard let time = Time(time) else {
|
||||
return .never
|
||||
}
|
||||
|
||||
return at(time)
|
||||
}
|
||||
|
||||
/// Returns a plan at the specific time.
|
||||
/// Creates a plan with time specified.
|
||||
///
|
||||
/// .at(1) => 01
|
||||
/// .at(1, 2) => 01:02
|
||||
/// .at(1, 2, 3) => 01:02:03
|
||||
/// .at(1, 2, 3, 456) => 01:02:03.456
|
||||
///
|
||||
/// - Note: Returns `Plan.never` if given no parameters.
|
||||
public func at(_ time: Int...) -> Plan {
|
||||
return self.at(time)
|
||||
}
|
||||
|
||||
/// Returns a plan at the specific time.
|
||||
/// Creates a plan with time specified.
|
||||
///
|
||||
/// .at([1]) => 01
|
||||
/// .at([1, 2]) => 01:02
|
||||
/// .at([1, 2, 3]) => 01:02:03
|
||||
/// .at([1, 2, 3, 456]) => 01:02:03.456
|
||||
///
|
||||
/// - Note: Returns `Plan.never` if given an empty array.
|
||||
public func at(_ time: [Int]) -> Plan {
|
||||
guard !time.isEmpty, !self.plan.isNever() else { return .never }
|
||||
if plan.isNever() || time.isEmpty { return .never }
|
||||
|
||||
let hour = time[0]
|
||||
let minute = time.count > 1 ? time[1] : 0
|
||||
let second = time.count > 2 ? time[2] : 0
|
||||
let nanosecond = time.count > 3 ? time[3]: 0
|
||||
|
||||
guard let time = Time(hour: hour, minute: minute, second: second, nanosecond: nanosecond) else {
|
||||
guard let time = Time(
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
nanosecond: nanosecond
|
||||
) else {
|
||||
return Plan.never
|
||||
}
|
||||
return at(time)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a plan that executes the task every specific weekday.
|
||||
/// Creates a date middleware that executes the task on every specific week day.
|
||||
public static func every(_ weekday: Weekday) -> DateMiddleware {
|
||||
let plan = Plan.make { () -> AnyIterator<Date> in
|
||||
let calendar = Calendar.gregorian
|
||||
|
@ -419,14 +425,12 @@ extension Plan {
|
|||
return DateMiddleware(plan: plan)
|
||||
}
|
||||
|
||||
/// Creates a plan that executes the task every specific weekdays.
|
||||
/// - Note: Returns initialized with `Plan.never` if given no parameters.
|
||||
/// Creates a date middleware that executes the task on every specific week day.
|
||||
public static func every(_ weekdays: Weekday...) -> DateMiddleware {
|
||||
return Plan.every(weekdays)
|
||||
}
|
||||
|
||||
/// Creates a plan that executes the task every specific weekdays.
|
||||
/// - Note: Returns initialized with `Plan.never` if given an empty array.
|
||||
/// Creates a date middleware that executes the task on every specific week day.
|
||||
public static func every(_ weekdays: [Weekday]) -> DateMiddleware {
|
||||
guard !weekdays.isEmpty else { return .init(plan: .never) }
|
||||
|
||||
|
@ -439,7 +443,7 @@ extension Plan {
|
|||
return DateMiddleware(plan: plan)
|
||||
}
|
||||
|
||||
/// Creates a plan that executes the task every specific day in the month.
|
||||
/// Creates a date middleware that executes the task on every specific month day.
|
||||
public static func every(_ monthday: Monthday) -> DateMiddleware {
|
||||
let plan = Plan.make { () -> AnyIterator<Date> in
|
||||
let calendar = Calendar.gregorian
|
||||
|
@ -459,14 +463,12 @@ extension Plan {
|
|||
return DateMiddleware(plan: plan)
|
||||
}
|
||||
|
||||
/// Creates a plan that executes the task every specific days in the months.
|
||||
/// - Note: Returns initialized with `Plan.never` if given no parameters.
|
||||
/// Creates a date middleware that executes the task on every specific month day.
|
||||
public static func every(_ mondays: Monthday...) -> DateMiddleware {
|
||||
return Plan.every(mondays)
|
||||
}
|
||||
|
||||
/// Creates a plan that executes the task every specific days in the months.
|
||||
/// - Note: Returns initialized with `Plan.never` if given an empty array.
|
||||
/// Creates a date middleware that executes the task on every specific month day.
|
||||
public static func every(_ mondays: [Monthday]) -> DateMiddleware {
|
||||
guard !mondays.isEmpty else { return .init(plan: .never) }
|
||||
|
||||
|
@ -484,11 +486,12 @@ extension Plan {
|
|||
|
||||
/// Returns a Boolean value indicating whether this plan is empty.
|
||||
public func isNever() -> Bool {
|
||||
return iSeq.makeIterator().next() == nil
|
||||
return seq.makeIterator().next() == nil
|
||||
}
|
||||
}
|
||||
|
||||
extension Plan {
|
||||
|
||||
/// Creates a new plan that is offset by the specified interval in the
|
||||
/// closure body.
|
||||
///
|
||||
|
@ -497,23 +500,15 @@ extension Plan {
|
|||
///
|
||||
/// If the returned interval offset is `nil`, then no offset is added
|
||||
/// to that next-run date.
|
||||
public func offset(by intervalOffset: @escaping () -> Interval?) -> Plan {
|
||||
public func offset(by interval: @autoclosure @escaping () -> Interval?) -> Plan {
|
||||
return Plan.make { () -> AnyIterator<Interval> in
|
||||
let it = self.makeIterator()
|
||||
return AnyIterator {
|
||||
if let next = it.next() {
|
||||
return next + (intervalOffset() ?? 0.second)
|
||||
return next + (interval() ?? 0.second)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new plan that is offset by the specified interval.
|
||||
///
|
||||
/// If the specified interval offset is `nil`, then no offset is
|
||||
/// added to the plan (ie. it stays the same).
|
||||
public func offset(by intervalOffset: Interval?) -> Plan {
|
||||
return self.offset(by: { intervalOffset })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,38 +5,42 @@ extension Plan {
|
|||
/// Schedules a task with this plan.
|
||||
///
|
||||
/// When time is up, the task will be executed on current thread. It behaves
|
||||
/// like a `Timer`, so you have to make sure the current thread has a
|
||||
/// runloop available.
|
||||
/// like a `Timer`, so you need to make sure that the current thread has a
|
||||
/// available runloop.
|
||||
///
|
||||
/// Since this method relies on run loop, it is recommended to use
|
||||
/// Since this method relies on run loop, it is remove recommended to use
|
||||
/// `do(queue: _, onElapse: _)`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - mode: The mode in which to add the task.
|
||||
/// - onElapse: The action to do when time is out.
|
||||
/// - mode: The mode to which the block should be added.
|
||||
/// - block: A block to be executed when time is up.
|
||||
/// - Returns: The task just created.
|
||||
public func `do`(mode: RunLoop.Mode = .common,
|
||||
onElapse: @escaping (Task) -> Void) -> Task {
|
||||
return RunLoopTask(plan: self, mode: mode, onElapse: onElapse)
|
||||
public func `do`(
|
||||
mode: RunLoop.Mode = .common,
|
||||
block: @escaping (Task) -> Void
|
||||
) -> Task {
|
||||
return RunLoopTask(plan: self, mode: mode, block: block)
|
||||
}
|
||||
|
||||
/// Schedules a task with this plan.
|
||||
///
|
||||
/// When time is up, the task will be executed on current thread. It behaves
|
||||
/// like a `Timer`, so you have to make sure the current thread has a
|
||||
/// runloop available.
|
||||
/// like a `Timer`, so you need to make sure that the current thread has a
|
||||
/// available runloop.
|
||||
///
|
||||
/// Since this method relies on run loop, it is recommended to use
|
||||
/// Since this method relies on run loop, it is remove recommended to use
|
||||
/// `do(queue: _, onElapse: _)`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - mode: The mode in which to add the task.
|
||||
/// - onElapse: The action to do when time is out.
|
||||
/// - mode: The mode to which the block should be added.
|
||||
/// - block: A block to be executed when time is up.
|
||||
/// - Returns: The task just created.
|
||||
public func `do`(mode: RunLoop.Mode = .common,
|
||||
onElapse: @escaping () -> Void) -> Task {
|
||||
return self.do(mode: mode) { (_) in
|
||||
onElapse()
|
||||
public func `do`(
|
||||
mode: RunLoop.Mode = .common,
|
||||
block: @escaping () -> Void
|
||||
) -> Task {
|
||||
return self.do(mode: mode) { _ in
|
||||
block()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,24 +49,26 @@ private final class RunLoopTask: Task {
|
|||
|
||||
var timer: Timer!
|
||||
|
||||
init(plan: Plan, mode: RunLoop.Mode, onElapse: @escaping (Task) -> Void) {
|
||||
init(
|
||||
plan: Plan,
|
||||
mode: RunLoop.Mode,
|
||||
block: @escaping (Task) -> Void
|
||||
) {
|
||||
super.init(plan: plan, queue: nil) { (task) in
|
||||
guard let task = task as? RunLoopTask, let timer = task.timer else { return }
|
||||
timer.fireDate = Date()
|
||||
}
|
||||
|
||||
weak var this: Task?
|
||||
|
||||
let distant = Date.distantFuture.timeIntervalSinceReferenceDate
|
||||
timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, distant, distant, 0, 0) { _ in
|
||||
guard let task = this else { return }
|
||||
onElapse(task)
|
||||
timer = Timer(
|
||||
fire: Date.distantFuture,
|
||||
interval: .greatestFiniteMagnitude,
|
||||
repeats: false
|
||||
) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
block(self)
|
||||
}
|
||||
|
||||
RunLoop.current.add(timer, forMode: mode)
|
||||
|
||||
super.init(plan: plan, queue: nil) { (task) in
|
||||
guard let task = task as? RunLoopTask else { return }
|
||||
task.timer.fireDate = Date()
|
||||
}
|
||||
|
||||
this = self
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
|
|
@ -13,7 +13,7 @@ extension BagKey {
|
|||
}
|
||||
}
|
||||
|
||||
/// `Task` represents a timed task.
|
||||
/// `Task` represents a timing task.
|
||||
open class Task {
|
||||
|
||||
/// The unique id of this task.
|
||||
|
@ -21,50 +21,58 @@ open class Task {
|
|||
|
||||
public typealias Action = (Task) -> Void
|
||||
|
||||
private let _mutex = NSRecursiveLock()
|
||||
private let _lock = NSRecursiveLock()
|
||||
|
||||
private var _iterator: AnyIterator<Interval>
|
||||
private var _timer: DispatchSourceTimer
|
||||
|
||||
private lazy var _onElapseActions = Bag<Action>()
|
||||
private lazy var _actions = Bag<Action>()
|
||||
|
||||
private lazy var _suspensions: UInt64 = 0
|
||||
private lazy var _timeline = Timeline()
|
||||
private lazy var _suspensionCount: UInt64 = 0
|
||||
private lazy var _executionCount: Int = 0
|
||||
|
||||
private lazy var _countOfExecutions: Int = 0
|
||||
private var _firstExecutionDate: Date?
|
||||
private var _lastExecutionDate: Date?
|
||||
private var _estimatedNextExecutionDate: Date?
|
||||
|
||||
private lazy var _lifetime: Interval = Int.max.seconds
|
||||
private lazy var _lifetimeTimer: DispatchSourceTimer = {
|
||||
let timer = DispatchSource.makeTimerSource()
|
||||
timer.setEventHandler { [weak self] in
|
||||
self?.cancel()
|
||||
/// The date of creation.
|
||||
public let creationDate = Date()
|
||||
|
||||
/// The date of first execution.
|
||||
open var firstExecutionDate: Date? {
|
||||
return _lock.withLock { _firstExecutionDate }
|
||||
}
|
||||
|
||||
/// The date of last execution.
|
||||
open var lastExecutionDate: Date? {
|
||||
return _lock.withLock { _lastExecutionDate }
|
||||
}
|
||||
|
||||
/// The date of estimated next execution.
|
||||
open var estimatedNextExecutionDate: Date? {
|
||||
return _lock.withLock { _estimatedNextExecutionDate }
|
||||
}
|
||||
timer.schedule(after: _lifetime)
|
||||
timer.resume()
|
||||
return timer
|
||||
}()
|
||||
|
||||
private weak var _taskCenter: TaskCenter?
|
||||
|
||||
/// The task center which this task currently in.
|
||||
/// The task center to which this task currently belongs.
|
||||
open var taskCenter: TaskCenter? {
|
||||
return _taskCenter
|
||||
return _lock.withLock { _taskCenter }
|
||||
}
|
||||
|
||||
/// The mutex used to guard task center operations.
|
||||
private let _taskCenterLock = NSRecursiveLock()
|
||||
|
||||
/// Adds this task to the given task center.
|
||||
///
|
||||
/// If this task is already in a task center, it will be removed from that center first.
|
||||
func addToTaskCenter(_ center: TaskCenter) {
|
||||
_taskCenterLock.lock()
|
||||
defer { _taskCenterLock.unlock() }
|
||||
|
||||
if _taskCenter === center { return }
|
||||
|
||||
_taskCenter?.remove(self)
|
||||
let c = _taskCenter
|
||||
_taskCenter = center
|
||||
|
||||
c?.remove(self)
|
||||
}
|
||||
|
||||
/// Removes this task from the given task center.
|
||||
|
@ -74,24 +82,25 @@ open class Task {
|
|||
|
||||
if _taskCenter !== center { return }
|
||||
|
||||
_taskCenter?.remove(self)
|
||||
_taskCenter = nil
|
||||
center.remove(self)
|
||||
}
|
||||
|
||||
/// Initializes a normal task with specified plan and dispatch queue.
|
||||
/// Initializes a timing task.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - plan: The plan.
|
||||
/// - queue: The dispatch queue to which all actions should be added.
|
||||
/// - onElapse: The action to do when time is out.
|
||||
init(plan: Plan,
|
||||
/// - queue: The dispatch queue to which the block should be dispatched.
|
||||
/// - block: A block to be executed when time is up.
|
||||
init(
|
||||
plan: Plan,
|
||||
queue: DispatchQueue?,
|
||||
onElapse: @escaping (Task) -> Void) {
|
||||
|
||||
block: @escaping (Task) -> Void
|
||||
) {
|
||||
_iterator = plan.makeIterator()
|
||||
_timer = DispatchSource.makeTimerSource(queue: queue)
|
||||
|
||||
_onElapseActions.append(onElapse)
|
||||
_actions.append(block)
|
||||
|
||||
_timer.setEventHandler { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
@ -100,17 +109,18 @@ open class Task {
|
|||
|
||||
if let interval = _iterator.next(), !interval.isNegative {
|
||||
_timer.schedule(after: interval)
|
||||
_timeline.estimatedNextExecution = Date().adding(interval)
|
||||
_estimatedNextExecutionDate = Date().adding(interval)
|
||||
}
|
||||
|
||||
_timer.resume()
|
||||
|
||||
TaskCenter.default.add(self)
|
||||
}
|
||||
|
||||
deinit {
|
||||
while _suspensions > 0 {
|
||||
while _suspensionCount > 0 {
|
||||
_timer.resume()
|
||||
_suspensions -= 1
|
||||
_suspensionCount -= 1
|
||||
}
|
||||
|
||||
cancel()
|
||||
|
@ -118,226 +128,144 @@ open class Task {
|
|||
taskCenter?.remove(self)
|
||||
}
|
||||
|
||||
private func scheduleNext() {
|
||||
_mutex.withLockVoid {
|
||||
private func elapse() {
|
||||
scheduleNextExecution()
|
||||
execute()
|
||||
}
|
||||
|
||||
private func scheduleNextExecution() {
|
||||
_lock.withLockVoid {
|
||||
let now = Date()
|
||||
var estimated = _timeline.estimatedNextExecution ?? now
|
||||
var estimated = _estimatedNextExecutionDate ?? now
|
||||
repeat {
|
||||
guard let interval = _iterator.next(), !interval.isNegative else {
|
||||
_timeline.estimatedNextExecution = nil
|
||||
_estimatedNextExecutionDate = nil
|
||||
return
|
||||
}
|
||||
estimated = estimated.adding(interval)
|
||||
} while (estimated < now)
|
||||
|
||||
_timeline.estimatedNextExecution = estimated
|
||||
_timer.schedule(after: _timeline.estimatedNextExecution!.interval(since: now))
|
||||
_estimatedNextExecutionDate = estimated
|
||||
_timer.schedule(after: _estimatedNextExecutionDate!.interval(since: now))
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute this task now, without disrupting its plan.
|
||||
public func execute() {
|
||||
let actions = _mutex.withLock { () -> Bag<Task.Action> in
|
||||
/// Execute this task now, without interrupting its plan.
|
||||
open func execute() {
|
||||
let actions = _lock.withLock { () -> Bag<Task.Action> in
|
||||
let now = Date()
|
||||
if _timeline.firstExecution == nil {
|
||||
_timeline.firstExecution = now
|
||||
if _firstExecutionDate == nil {
|
||||
_firstExecutionDate = now
|
||||
}
|
||||
_timeline.lastExecution = now
|
||||
_countOfExecutions += 1
|
||||
return _onElapseActions
|
||||
_lastExecutionDate = now
|
||||
_executionCount += 1
|
||||
return _actions
|
||||
}
|
||||
actions.forEach { $0(self) }
|
||||
}
|
||||
|
||||
private func elapse() {
|
||||
scheduleNext()
|
||||
execute()
|
||||
}
|
||||
|
||||
/// Host this task to an object, that is, when the object deallocates, this task will be cancelled.
|
||||
#if canImport(ObjectiveC)
|
||||
open func host(on target: AnyObject) {
|
||||
open func host(to target: AnyObject) {
|
||||
DeinitObserver.observe(target) { [weak self] in
|
||||
self?.cancel()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// The number of times the task has been executed.
|
||||
public var countOfExecutions: Int {
|
||||
return _mutex.withLock {
|
||||
_countOfExecutions
|
||||
/// The number of task executions.
|
||||
public var executionCount: Int {
|
||||
return _lock.withLock {
|
||||
_executionCount
|
||||
}
|
||||
}
|
||||
|
||||
/// A Boolean indicating whether the task was canceled.
|
||||
public var isCancelled: Bool {
|
||||
return _mutex.withLock {
|
||||
return _lock.withLock {
|
||||
_timer.isCancelled
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Manage
|
||||
|
||||
/// Reschedules this task with the new plan.
|
||||
public func reschedule(_ new: Plan) {
|
||||
_mutex.withLockVoid {
|
||||
_lock.withLockVoid {
|
||||
_iterator = new.makeIterator()
|
||||
}
|
||||
scheduleNext()
|
||||
scheduleNextExecution()
|
||||
}
|
||||
|
||||
/// Suspensions of this task.
|
||||
public var suspensions: UInt64 {
|
||||
return _mutex.withLock {
|
||||
_suspensions
|
||||
/// The number of task suspensions.
|
||||
public var suspensionCount: UInt64 {
|
||||
return _lock.withLock {
|
||||
_suspensionCount
|
||||
}
|
||||
}
|
||||
|
||||
/// Suspends this task.
|
||||
public func suspend() {
|
||||
_mutex.withLockVoid {
|
||||
if _suspensions < UInt64.max {
|
||||
_lock.withLockVoid {
|
||||
if _suspensionCount < UInt64.max {
|
||||
_timer.suspend()
|
||||
_suspensions += 1
|
||||
_suspensionCount += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resumes this task.
|
||||
public func resume() {
|
||||
_mutex.withLockVoid {
|
||||
if _suspensions > 0 {
|
||||
_lock.withLockVoid {
|
||||
if _suspensionCount > 0 {
|
||||
_timer.resume()
|
||||
_suspensions -= 1
|
||||
_suspensionCount -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancels this task.
|
||||
public func cancel() {
|
||||
_mutex.withLockVoid {
|
||||
_lock.withLockVoid {
|
||||
_timer.cancel()
|
||||
}
|
||||
TaskCenter.default.remove(self)
|
||||
}
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
/// The snapshot timeline of this task.
|
||||
public var timeline: Timeline {
|
||||
return _mutex.withLock {
|
||||
_timeline
|
||||
}
|
||||
}
|
||||
|
||||
/// The lifetime of this task.
|
||||
public var lifetime: Interval {
|
||||
return _mutex.withLock {
|
||||
_lifetime
|
||||
}
|
||||
}
|
||||
|
||||
/// The rest of lifetime.
|
||||
public var restOfLifetime: Interval {
|
||||
return _mutex.withLock {
|
||||
_lifetime - Date().interval(since: _timeline.initialization)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a new lifetime for this task.
|
||||
///
|
||||
/// If this task has already ended its lifetime, setting will fail,
|
||||
/// if new lifetime is shorter than its age, setting will fail, too.
|
||||
///
|
||||
/// - Returns: `true` if set successfully, `false` if not.
|
||||
@discardableResult
|
||||
public func setLifetime(_ interval: Interval) -> Bool {
|
||||
if restOfLifetime.isNegative {
|
||||
return false
|
||||
}
|
||||
|
||||
_mutex.lock()
|
||||
let age = Date().interval(since: _timeline.initialization)
|
||||
guard age.isShorter(than: interval) else {
|
||||
_mutex.unlock()
|
||||
return false
|
||||
}
|
||||
|
||||
_lifetime = interval
|
||||
_lifetimeTimer.schedule(after: interval - age)
|
||||
_mutex.unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
/// Add an interval to this task's lifetime.
|
||||
///
|
||||
/// If this task has already ended its lifetime, adding will fail,
|
||||
/// if new lifetime is shorter than its age, adding will fail, too.
|
||||
///
|
||||
/// - Returns: `true` if set successfully, `false` if not.
|
||||
@discardableResult
|
||||
public func addLifetime(_ interval: Interval) -> Bool {
|
||||
var rest = restOfLifetime
|
||||
if rest.isNegative { return false }
|
||||
rest += interval
|
||||
if rest.isNegative { return false }
|
||||
_mutex.withLockVoid {
|
||||
_lifetime += interval
|
||||
_lifetimeTimer.schedule(after: rest)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/// Subtract an interval to this task's lifetime.
|
||||
///
|
||||
/// If this task has already ended its lifetime, subtracting will fail,
|
||||
/// if new lifetime is shorter than its age, subtracting will fail, too.
|
||||
///
|
||||
/// - Returns: `true` if set successfully, `false` if not.
|
||||
@discardableResult
|
||||
public func subtractLifetime(_ interval: Interval) -> Bool {
|
||||
return addLifetime(interval.negated)
|
||||
}
|
||||
|
||||
// MARK: - Action
|
||||
|
||||
/// The number of actions in this task.
|
||||
public var countOfActions: Int {
|
||||
return _mutex.withLock {
|
||||
_onElapseActions.count
|
||||
return _lock.withLock {
|
||||
_actions.count
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds action to this task.
|
||||
@discardableResult
|
||||
public func addAction(_ action: @escaping (Task) -> Void) -> ActionKey {
|
||||
return _mutex.withLock {
|
||||
return _onElapseActions.append(action).asActionKey()
|
||||
return _lock.withLock {
|
||||
return _actions.append(action).asActionKey()
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes action by key from this task.
|
||||
public func removeAction(byKey key: ActionKey) {
|
||||
_mutex.withLockVoid {
|
||||
_ = _onElapseActions.removeValue(for: key.bagKey)
|
||||
_lock.withLockVoid {
|
||||
_ = _actions.removeValue(for: key.bagKey)
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes all actions from this task.
|
||||
public func removeAllActions() {
|
||||
_mutex.withLockVoid {
|
||||
_onElapseActions.removeAll()
|
||||
_lock.withLockVoid {
|
||||
_actions.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Tag
|
||||
open func add(to: TaskCenter) {
|
||||
_mutex.lock()
|
||||
}
|
||||
}
|
||||
|
||||
extension Task: Hashable {
|
||||
|
||||
/// Hashes the essential components of this value by feeding them into the given hasher.
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
}
|
||||
|
@ -347,25 +275,3 @@ extension Task: Hashable {
|
|||
return lhs === rhs
|
||||
}
|
||||
}
|
||||
|
||||
extension Task: CustomStringConvertible {
|
||||
|
||||
/// A textual representation of this task.
|
||||
public var description: String {
|
||||
return "Task: { " +
|
||||
"\"isCancelled\": \(_timer.isCancelled), " +
|
||||
"\"countOfElapseActions\": \(_onElapseActions.count), " +
|
||||
"\"countOfExecutions\": \(_countOfExecutions), " +
|
||||
"\"lifeTime\": \(_lifetime), " +
|
||||
"\"timeline\": \(_timeline)" +
|
||||
" }"
|
||||
}
|
||||
}
|
||||
|
||||
extension Task: CustomDebugStringConvertible {
|
||||
|
||||
/// A textual representation of this task for debugging.
|
||||
public var debugDescription: String {
|
||||
return description
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,8 +30,8 @@ open class TaskCenter {
|
|||
|
||||
private let lock = NSLock()
|
||||
|
||||
private var tasksOfTag: [String: Set<TaskBox>] = [:]
|
||||
private var tagsOfTask: [TaskBox: Set<String>] = [:]
|
||||
private var tags: [String: Set<TaskBox>] = [:]
|
||||
private var tasks: [TaskBox: Set<String>] = [:]
|
||||
|
||||
/// Default task center.
|
||||
open class var `default`: TaskCenter {
|
||||
|
@ -39,14 +39,12 @@ open class TaskCenter {
|
|||
}
|
||||
|
||||
/// Adds the given task to this center.
|
||||
///
|
||||
/// Center won't retain the task.
|
||||
open func add(_ task: Task) {
|
||||
task.addToTaskCenter(self)
|
||||
|
||||
lock.withLockVoid {
|
||||
let box = TaskBox(task)
|
||||
tagsOfTask[box] = []
|
||||
self.tasks[box] = []
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,15 +54,14 @@ open class TaskCenter {
|
|||
|
||||
lock.withLockVoid {
|
||||
let box = TaskBox(task)
|
||||
if let tags = self.tagsOfTask[box] {
|
||||
if let tags = self.tasks[box] {
|
||||
for tag in tags {
|
||||
self.tasksOfTag[tag]?.remove(box)
|
||||
|
||||
if self.tasksOfTag[tag]?.count == 0 {
|
||||
self.tasksOfTag[tag] = nil
|
||||
self.tags[tag]?.remove(box)
|
||||
if self.tags[tag]?.count == 0 {
|
||||
self.tags[tag] = nil
|
||||
}
|
||||
}
|
||||
self.tagsOfTask[box] = nil
|
||||
self.tasks[box] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,15 +81,12 @@ open class TaskCenter {
|
|||
|
||||
lock.withLockVoid {
|
||||
let box = TaskBox(task)
|
||||
if tagsOfTask[box] == nil {
|
||||
tagsOfTask[box] = []
|
||||
}
|
||||
for tag in tags {
|
||||
tagsOfTask[box]?.insert(tag)
|
||||
if tasksOfTag[tag] == nil {
|
||||
tasksOfTag[tag] = []
|
||||
tasks[box]?.insert(tag)
|
||||
if self.tags[tag] == nil {
|
||||
self.tags[tag] = []
|
||||
}
|
||||
tasksOfTag[tag]?.insert(box)
|
||||
self.tags[tag]?.insert(box)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -113,64 +107,67 @@ open class TaskCenter {
|
|||
lock.withLockVoid {
|
||||
let box = TaskBox(task)
|
||||
for tag in tags {
|
||||
tagsOfTask[box]?.remove(tag)
|
||||
tasksOfTag[tag]?.remove(box)
|
||||
self.tasks[box]?.remove(tag)
|
||||
self.tags[tag]?.remove(box)
|
||||
if self.tags[tag]?.count == 0 {
|
||||
self.tags[tag] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all tags on the task.
|
||||
/// Returns all tags for the task.
|
||||
///
|
||||
/// If the task is not in this center, return an empty array.
|
||||
open func tagsForTask(_ task: Task) -> [String] {
|
||||
open func tags(forTask task: Task) -> [String] {
|
||||
guard task.taskCenter === self else { return [] }
|
||||
|
||||
return lock.withLock {
|
||||
Array(tagsOfTask[TaskBox(task)] ?? [])
|
||||
Array(tasks[TaskBox(task)] ?? [])
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all tasks that have the tag.
|
||||
open func tasksForTag(_ tag: String) -> [Task] {
|
||||
/// Returns all tasks for the tag.
|
||||
open func tasks(forTag tag: String) -> [Task] {
|
||||
return lock.withLock {
|
||||
tasksOfTag[tag]?.compactMap { $0.task } ?? []
|
||||
tags[tag]?.compactMap { $0.task } ?? []
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all tasks in this center.
|
||||
open var allTasks: [Task] {
|
||||
return lock.withLock {
|
||||
tagsOfTask.compactMap { $0.key.task }
|
||||
tasks.compactMap { $0.key.task }
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all existing tags in this center.
|
||||
/// Returns all tags in this center.
|
||||
open var allTags: [String] {
|
||||
return lock.withLock {
|
||||
tasksOfTag.map { $0.key }
|
||||
tags.map { $0.key }
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes all tasks from this center.
|
||||
open func removeAll() {
|
||||
lock.withLockVoid {
|
||||
tagsOfTask = [:]
|
||||
tasksOfTag = [:]
|
||||
tasks = [:]
|
||||
tags = [:]
|
||||
}
|
||||
}
|
||||
|
||||
/// Suspends all tasks that have the tag.
|
||||
open func suspendByTag(_ tag: String) {
|
||||
tasksForTag(tag).forEach { $0.suspend() }
|
||||
/// Suspends all tasks by tag.
|
||||
open func suspend(byTag tag: String) {
|
||||
tasks(forTag: tag).forEach { $0.suspend() }
|
||||
}
|
||||
|
||||
/// Resumes all tasks that have the tag.
|
||||
open func resumeByTag(_ tag: String) {
|
||||
tasksForTag(tag).forEach { $0.resume() }
|
||||
/// Resumes all tasks by tag.
|
||||
open func resume(byTag tag: String) {
|
||||
tasks(forTag: tag).forEach { $0.resume() }
|
||||
}
|
||||
|
||||
/// Cancels all tasks that have the tag.
|
||||
open func cancelByTag(_ tag: String) {
|
||||
tasksForTag(tag).forEach { $0.cancel() }
|
||||
/// Cancels all tasks by tag.
|
||||
open func cancel(byTag tag: String) {
|
||||
tasks(forTag: tag).forEach { $0.cancel() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,8 +48,15 @@ public struct Time {
|
|||
public init?(_ string: String) {
|
||||
let pattern = "^(\\d{1,2})(:(\\d{1,2})(:(\\d{1,2})(.(\\d{1,3}))?)?)?( (am|AM|pm|PM))?$"
|
||||
|
||||
// swiftlint:disable force_try
|
||||
let regexp = try! NSRegularExpression(pattern: pattern, options: [])
|
||||
guard let matches = regexp.matches(in: string, options: [], range: NSRange(location: 0, length: string.count)).first else { return nil }
|
||||
guard let matches = regexp.matches(
|
||||
in: string,
|
||||
options: [],
|
||||
range: NSRange(location: 0, length: string.count)).first
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var hasAM = false
|
||||
var hasPM = false
|
||||
|
@ -87,9 +94,9 @@ public struct Time {
|
|||
|
||||
/// Returns a dateComponenets of the time, using gregorian calender and
|
||||
/// current time zone.
|
||||
public func asDateComponents() -> DateComponents {
|
||||
public func asDateComponents(_ timeZone: TimeZone = .current) -> DateComponents {
|
||||
return DateComponents(calendar: Calendar.gregorian,
|
||||
timeZone: TimeZone.current,
|
||||
timeZone: timeZone,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
second: second,
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
import Foundation
|
||||
|
||||
/// `Timeline` records a task's lifecycle.
|
||||
public struct Timeline {
|
||||
|
||||
/// The time of initialization.
|
||||
public let initialization = Date()
|
||||
|
||||
/// The time of first execution.
|
||||
public internal(set) var firstExecution: Date?
|
||||
|
||||
/// The time of last execution.
|
||||
public internal(set) var lastExecution: Date?
|
||||
|
||||
/// The time of estimated next execution.
|
||||
public internal(set) var estimatedNextExecution: Date?
|
||||
|
||||
init() { }
|
||||
}
|
||||
|
||||
extension Timeline: CustomStringConvertible {
|
||||
|
||||
/// A textual representation of this timeline.
|
||||
public var description: String {
|
||||
enum Lazy {
|
||||
static let fmt = ISO8601DateFormatter()
|
||||
}
|
||||
|
||||
let desc = { (d: Date?) -> String in
|
||||
guard let d = d else { return "nil" }
|
||||
return Lazy.fmt.string(from: d)
|
||||
}
|
||||
|
||||
return "Timeline: { " +
|
||||
"\"initialization\": \(desc(initialization))" +
|
||||
"\"firstExecution\": \(desc(firstExecution)), " +
|
||||
"\"lastExecution\": \(desc(lastExecution)), " +
|
||||
"\"estimatedNextExecution\": \(desc(estimatedNextExecution))" +
|
||||
" }"
|
||||
}
|
||||
}
|
||||
|
||||
extension Timeline: CustomDebugStringConvertible {
|
||||
|
||||
/// A textual representation of this timeline for debugging.
|
||||
public var debugDescription: String {
|
||||
return description
|
||||
}
|
||||
}
|
|
@ -7,10 +7,10 @@ public enum Weekday: Int {
|
|||
|
||||
/// Returns dateComponenets of the weekday, using gregorian calender and
|
||||
/// current time zone.
|
||||
public func asDateComponents() -> DateComponents {
|
||||
public func asDateComponents(_ timeZone: TimeZone = .current) -> DateComponents {
|
||||
return DateComponents(
|
||||
calendar: Calendar.gregorian,
|
||||
timeZone: TimeZone.current,
|
||||
timeZone: timeZone,
|
||||
weekday: rawValue)
|
||||
}
|
||||
}
|
||||
|
@ -18,8 +18,10 @@ public enum Weekday: Int {
|
|||
extension Date {
|
||||
|
||||
/// Returns a Boolean value indicating whether this date is the weekday in current time zone.
|
||||
public func `is`(_ weekday: Weekday) -> Bool {
|
||||
return Calendar.gregorian.component(.weekday, from: self) == weekday.rawValue
|
||||
public func `is`(_ weekday: Weekday, in timeZone: TimeZone = .current) -> Bool {
|
||||
var cal = Calendar.gregorian
|
||||
cal.timeZone = timeZone
|
||||
return cal.component(.weekday, from: self) == weekday.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,6 @@ final class AtomicTests: XCTestCase {
|
|||
("testRead", testRead),
|
||||
("testReadVoid", testReadVoid),
|
||||
("testWrite", testWrite),
|
||||
("testWriteVoid", testWriteVoid),
|
||||
("testWriteVoid", testWriteVoid)
|
||||
]
|
||||
}
|
||||
|
|
|
@ -18,14 +18,14 @@ final class ExtensionsTests: XCTestCase {
|
|||
|
||||
func testStartOfToday() {
|
||||
let components = Date().startOfToday.dateComponents
|
||||
guard
|
||||
let h = components.hour,
|
||||
let m = components.minute,
|
||||
|
||||
let h = components.hour
|
||||
let m = components.minute
|
||||
let s = components.second
|
||||
else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
XCTAssertNotNil(h)
|
||||
XCTAssertNotNil(m)
|
||||
XCTAssertNotNil(s)
|
||||
|
||||
XCTAssertEqual(h, 0)
|
||||
XCTAssertEqual(m, 0)
|
||||
XCTAssertEqual(s, 0)
|
||||
|
|
|
@ -64,7 +64,7 @@ extension Plan {
|
|||
extension DispatchQueue {
|
||||
|
||||
func async(after interval: Interval, execute body: @escaping () -> Void) {
|
||||
asyncAfter(deadline: .now() + interval.asSeconds(), execute: body)
|
||||
asyncAfter(wallDeadline: .now() + interval.asSeconds(), execute: body)
|
||||
}
|
||||
|
||||
static func `is`(_ queue: DispatchQueue) -> Bool {
|
||||
|
@ -76,3 +76,8 @@ extension DispatchQueue {
|
|||
return DispatchQueue.getSpecific(key: key) != nil
|
||||
}
|
||||
}
|
||||
|
||||
extension TimeZone {
|
||||
|
||||
static let shanghai = TimeZone(identifier: "Asia/Shanghai")!
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ final class IntervalTests: XCTestCase {
|
|||
func testCompare() {
|
||||
XCTAssertEqual((-1).second.compare(1.second), ComparisonResult.orderedAscending)
|
||||
XCTAssertEqual(8.days.compare(1.week), ComparisonResult.orderedDescending)
|
||||
XCTAssertEqual(1.day.compare(24.hours), ComparisonResult.orderedSame)
|
||||
|
||||
XCTAssertTrue(23.hours < 1.day)
|
||||
XCTAssertTrue(25.hours > 1.day)
|
||||
|
@ -105,6 +106,6 @@ final class IntervalTests: XCTestCase {
|
|||
("testAdding", testAdding),
|
||||
("testOperators", testOperators),
|
||||
("testAs", testAs),
|
||||
("testDate", testDate),
|
||||
("testDate", testDate)
|
||||
]
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ final class MonthdayTests: XCTestCase {
|
|||
func testIs() {
|
||||
// ! Be careful the time zone problem.
|
||||
let d = Date(year: 2019, month: 1, day: 1)
|
||||
XCTAssertTrue(d.is(.january(1)))
|
||||
XCTAssertTrue(d.is(.january(1), in: TimeZone.shanghai))
|
||||
}
|
||||
|
||||
func testAsDateComponents() {
|
||||
|
|
|
@ -29,6 +29,9 @@ final class PeriodTests: XCTestCase {
|
|||
Period.registerQuantifier("many", for: 100 * 1000)
|
||||
let p4 = Period("many days")
|
||||
XCTAssertEqual(p4!.days, 100 * 1000)
|
||||
|
||||
let p5 = Period("hi, 😈")
|
||||
XCTAssertNil(p5)
|
||||
}
|
||||
|
||||
func testAdd() {
|
||||
|
|
|
@ -3,34 +3,47 @@ import XCTest
|
|||
|
||||
final class PlanTests: XCTestCase {
|
||||
|
||||
let leeway = 0.01.seconds
|
||||
private let leeway = 0.01.seconds
|
||||
|
||||
func testMake() {
|
||||
let intervals = [1.second, 2.hours, 3.days, 4.weeks]
|
||||
let s0 = Plan.of(intervals[0], intervals[1], intervals[2], intervals[3])
|
||||
XCTAssertTrue(s0.makeIterator().isAlmostEqual(to: intervals, leeway: leeway))
|
||||
func testOfIntervals() {
|
||||
let ints = [1.second, 2.hours, 3.days, 4.weeks]
|
||||
let p = Plan.of(ints)
|
||||
XCTAssertTrue(p.makeIterator().isAlmostEqual(to: ints, leeway: leeway))
|
||||
}
|
||||
|
||||
let d0 = Date() + intervals[0]
|
||||
let d1 = d0 + intervals[1]
|
||||
let d2 = d1 + intervals[2]
|
||||
let d3 = d2 + intervals[3]
|
||||
func testOfDates() {
|
||||
let ints = [1.second, 2.hours, 3.days, 4.weeks]
|
||||
|
||||
let s2 = Plan.of(d0, d1, d2, d3)
|
||||
XCTAssertTrue(s2.makeIterator().isAlmostEqual(to: intervals, leeway: leeway))
|
||||
let d0 = Date() + ints[0]
|
||||
let d1 = d0 + ints[1]
|
||||
let d2 = d1 + ints[2]
|
||||
let d3 = d2 + ints[3]
|
||||
|
||||
let longTime = (100 * 365).days
|
||||
XCTAssertTrue(Plan.distantPast.makeIterator().next()!.isLonger(than: longTime))
|
||||
XCTAssertTrue(Plan.distantFuture.makeIterator().next()!.isLonger(than: longTime))
|
||||
let p = Plan.of(d0, d1, d2, d3)
|
||||
XCTAssertTrue(p.makeIterator().isAlmostEqual(to: ints, leeway: leeway))
|
||||
}
|
||||
|
||||
func testDates() {
|
||||
let iterator = Plan.of(1.days, 2.weeks).dates.makeIterator()
|
||||
var next = iterator.next()
|
||||
XCTAssertNotNil(next)
|
||||
XCTAssertTrue(next!.intervalSinceNow.isAlmostEqual(to: 1.days, leeway: leeway))
|
||||
next = iterator.next()
|
||||
XCTAssertNotNil(next)
|
||||
XCTAssertTrue(next!.intervalSinceNow.isAlmostEqual(to: 2.weeks + 1.days, leeway: leeway))
|
||||
let dates = Plan.of(1.days, 2.weeks).dates.makeIterator()
|
||||
|
||||
var n = dates.next()
|
||||
XCTAssertNotNil(n)
|
||||
XCTAssertTrue(n!.intervalSinceNow.isAlmostEqual(to: 1.days, leeway: leeway))
|
||||
|
||||
n = dates.next()
|
||||
XCTAssertNotNil(n)
|
||||
XCTAssertTrue(n!.intervalSinceNow.isAlmostEqual(to: 2.weeks + 1.days, leeway: leeway))
|
||||
}
|
||||
|
||||
|
||||
func testDistant() {
|
||||
let distantPast = Plan.distantPast.makeIterator().next()
|
||||
XCTAssertNotNil(distantPast)
|
||||
XCTAssertTrue(distantPast!.isAlmostEqual(to: Date.distantPast.intervalSinceNow, leeway: leeway))
|
||||
|
||||
let distantFuture = Plan.distantFuture.makeIterator().next()
|
||||
XCTAssertNotNil(distantFuture)
|
||||
XCTAssertTrue(distantFuture!.isAlmostEqual(to: Date.distantFuture.intervalSinceNow, leeway: leeway))
|
||||
}
|
||||
|
||||
func testNever() {
|
||||
|
@ -38,32 +51,25 @@ final class PlanTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testConcat() {
|
||||
let s0: [Interval] = [1.second, 2.minutes, 3.hours]
|
||||
let s1: [Interval] = [4.days, 5.weeks]
|
||||
let s3 = Plan.of(s0).concat(Plan.of(s1))
|
||||
let s4 = Plan.of(s0 + s1)
|
||||
XCTAssertTrue(s3.isAlmostEqual(to: s4, leeway: leeway))
|
||||
let p0: [Interval] = [1.second, 2.minutes, 3.hours]
|
||||
let p1: [Interval] = [4.days, 5.weeks]
|
||||
let p2 = Plan.of(p0).concat(Plan.of(p1))
|
||||
let p3 = Plan.of(p0 + p1)
|
||||
XCTAssertTrue(p2.isAlmostEqual(to: p3, leeway: leeway))
|
||||
}
|
||||
|
||||
func testMerge() {
|
||||
let intervals0: [Interval] = [1.second, 2.minutes, 1.hour]
|
||||
let intervals1: [Interval] = [2.seconds, 1.minutes, 1.seconds]
|
||||
let scheudle0 = Plan.of(intervals0).merge(Plan.of(intervals1))
|
||||
let scheudle1 = Plan.of(1.second, 1.second, 1.minutes, 1.seconds, 58.seconds, 1.hour)
|
||||
XCTAssertTrue(scheudle0.isAlmostEqual(to: scheudle1, leeway: leeway))
|
||||
}
|
||||
|
||||
func testAt() {
|
||||
let s = Plan.at(Date() + 1.second)
|
||||
let next = s.makeIterator().next()
|
||||
XCTAssertNotNil(next)
|
||||
XCTAssertTrue(next!.isAlmostEqual(to: 1.second, leeway: leeway))
|
||||
let ints0: [Interval] = [1.second, 2.minutes, 1.hour]
|
||||
let ints1: [Interval] = [2.seconds, 1.minutes, 1.seconds]
|
||||
let p0 = Plan.of(ints0).merge(Plan.of(ints1))
|
||||
let p1 = Plan.of(1.second, 1.second, 1.minutes, 1.seconds, 58.seconds, 1.hour)
|
||||
XCTAssertTrue(p0.isAlmostEqual(to: p1, leeway: leeway))
|
||||
}
|
||||
|
||||
func testFirst() {
|
||||
var count = 10
|
||||
let s = Plan.every(1.second).first(count)
|
||||
let i = s.makeIterator()
|
||||
let p = Plan.every(1.second).first(count)
|
||||
let i = p.makeIterator()
|
||||
while count > 0 {
|
||||
XCTAssertNotNil(i.next())
|
||||
count -= 1
|
||||
|
@ -73,29 +79,36 @@ final class PlanTests: XCTestCase {
|
|||
|
||||
func testUntil() {
|
||||
let until = Date() + 10.seconds
|
||||
let s = Plan.every(1.second).until(until).dates
|
||||
let i = s.makeIterator()
|
||||
let p = Plan.every(1.second).until(until).dates
|
||||
let i = p.makeIterator()
|
||||
while let date = i.next() {
|
||||
XCTAssertLessThan(date, until)
|
||||
}
|
||||
}
|
||||
|
||||
func testNow() {
|
||||
let s0 = Plan.now
|
||||
let s1 = Plan.of(Date())
|
||||
XCTAssertTrue(s0.isAlmostEqual(to: s1, leeway: leeway))
|
||||
let p0 = Plan.now
|
||||
let p1 = Plan.of(Date())
|
||||
XCTAssertTrue(p0.isAlmostEqual(to: p1, leeway: leeway))
|
||||
}
|
||||
|
||||
func testAt() {
|
||||
let p = Plan.at(Date() + 1.second)
|
||||
let next = p.makeIterator().next()
|
||||
XCTAssertNotNil(next)
|
||||
XCTAssertTrue(next!.isAlmostEqual(to: 1.second, leeway: leeway))
|
||||
}
|
||||
|
||||
func testAfterAndRepeating() {
|
||||
let s0 = Plan.after(1.day, repeating: 1.hour).first(3)
|
||||
let s1 = Plan.of(1.day, 1.hour, 1.hour)
|
||||
XCTAssertTrue(s0.isAlmostEqual(to: s1, leeway: leeway))
|
||||
let p0 = Plan.after(1.day, repeating: 1.hour).first(3)
|
||||
let p1 = Plan.of(1.day, 1.hour, 1.hour)
|
||||
XCTAssertTrue(p0.isAlmostEqual(to: p1, leeway: leeway))
|
||||
}
|
||||
|
||||
func testEveryPeriod() {
|
||||
let s = Plan.every("1 year").first(10)
|
||||
let p = Plan.every("1 year").first(10)
|
||||
var date = Date()
|
||||
for i in s.dates {
|
||||
for i in p.dates {
|
||||
XCTAssertEqual(i.dateComponents.year!, date.dateComponents.year! + 1)
|
||||
XCTAssertEqual(i.dateComponents.month!, date.dateComponents.month!)
|
||||
XCTAssertEqual(i.dateComponents.day!, date.dateComponents.day!)
|
||||
|
@ -104,121 +117,47 @@ final class PlanTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testEveryWeekday() {
|
||||
let s = Plan.every(.friday, .monday).at("11:11:00").first(5)
|
||||
for i in s.dates {
|
||||
let p = Plan.every(.friday, .monday).at("11:11:00").first(5)
|
||||
for i in p.dates {
|
||||
XCTAssertTrue(i.dateComponents.weekday == 6 || i.dateComponents.weekday == 2)
|
||||
XCTAssertEqual(i.dateComponents.hour, 11)
|
||||
}
|
||||
}
|
||||
|
||||
func testEveryMonthday() {
|
||||
let s = Plan.every(.april(1), .october(1)).at(11, 11).first(5)
|
||||
for i in s.dates {
|
||||
let p = Plan.every(.april(1), .october(1)).at(11, 11).first(5)
|
||||
for i in p.dates {
|
||||
XCTAssertTrue(i.dateComponents.month == 4 || i.dateComponents.month == 10)
|
||||
XCTAssertEqual(i.dateComponents.day, 1)
|
||||
XCTAssertEqual(i.dateComponents.hour, 11)
|
||||
}
|
||||
}
|
||||
|
||||
func testPassingEmptyArrays() {
|
||||
XCTAssertTrue(Plan.of([Interval]()).isNever())
|
||||
XCTAssertTrue(Plan.of([Date]()).isNever())
|
||||
func testOffset() {
|
||||
let p1 = Plan.after(1.second).first(100)
|
||||
let p2 = p1.offset(by: 1.second).first(100)
|
||||
|
||||
XCTAssertTrue(Plan.every([Weekday]()).at(11, 11).isNever())
|
||||
XCTAssertTrue(Plan.every([Monthday]()).at(11, 11).isNever())
|
||||
|
||||
XCTAssertTrue(Plan.every(.monday).at([]).isNever())
|
||||
|
||||
XCTAssertTrue(Plan.every([Weekday]()).at("11:11:00").isNever())
|
||||
for (d1, d2) in zip(p1.dates, p2.dates) {
|
||||
XCTAssertTrue(d2.interval(since: d1).isAlmostEqual(to: 1.second, leeway: leeway))
|
||||
}
|
||||
|
||||
func testIntervalOffset() {
|
||||
// Non-offset plan
|
||||
let e1 = expectation(description: "testIntervalOffset_1")
|
||||
let plan1 = Plan.after(1.second)
|
||||
var date1: Date?
|
||||
|
||||
// Offset plan
|
||||
let e2 = expectation(description: "testIntervalOffset_2")
|
||||
let plan2 = plan1.offset(by: 1.second)
|
||||
var date2: Date?
|
||||
|
||||
let task1 = plan1.do { date1 = Date(); e1.fulfill() }
|
||||
let task2 = plan2.do { date2 = Date(); e2.fulfill() }
|
||||
_ = task1
|
||||
_ = task2
|
||||
|
||||
waitForExpectations(timeout: 3.5)
|
||||
|
||||
XCTAssertNotNil(date1)
|
||||
XCTAssertNotNil(date2)
|
||||
XCTAssertTrue(date2!.interval(since: date1!).isAlmostEqual(to: 1.second, leeway: 0.1.seconds))
|
||||
}
|
||||
|
||||
func testNegativeIntervalOffset() {
|
||||
// Non-offset plan
|
||||
let e1 = expectation(description: "testIntervalOffset_1")
|
||||
let plan1 = Plan.after(2.seconds)
|
||||
var date1: Date?
|
||||
|
||||
// Offset plan
|
||||
let e2 = expectation(description: "testIntervalOffset_2")
|
||||
let plan2 = plan1.offset(by: -1.second)
|
||||
var date2: Date?
|
||||
|
||||
let task1 = plan1.do { date1 = Date(); e1.fulfill() }
|
||||
let task2 = plan2.do { date2 = Date(); e2.fulfill() }
|
||||
_ = task1
|
||||
_ = task2
|
||||
|
||||
waitForExpectations(timeout: 2.5)
|
||||
|
||||
XCTAssertNotNil(date1)
|
||||
XCTAssertNotNil(date2)
|
||||
XCTAssertTrue(date2!.interval(since: date1!).isAlmostEqual(to: -1.second, leeway: 0.1.seconds))
|
||||
}
|
||||
|
||||
func testNilIntervalOffset() {
|
||||
// Non-offset plan
|
||||
let e1 = expectation(description: "testIntervalOffset_1")
|
||||
let plan1 = Plan.after(1.second)
|
||||
var date1: Date?
|
||||
|
||||
// Offset plan
|
||||
let e2 = expectation(description: "testIntervalOffset_2")
|
||||
let plan2 = plan1.offset(by: nil)
|
||||
var date2: Date?
|
||||
|
||||
let task1 = plan1.do { date1 = Date(); e1.fulfill() }
|
||||
let task2 = plan2.do { date2 = Date(); e2.fulfill() }
|
||||
|
||||
_ = task1
|
||||
_ = task2
|
||||
|
||||
waitForExpectations(timeout: 1.5)
|
||||
|
||||
XCTAssertNotNil(date1)
|
||||
XCTAssertNotNil(date2)
|
||||
XCTAssertTrue(date2!.interval(since: date1!).isAlmostEqual(to: 0.seconds, leeway: 0.1.seconds))
|
||||
}
|
||||
|
||||
static var allTests = [
|
||||
("testMake", testMake),
|
||||
("testOfIntervals", testOfIntervals),
|
||||
("testOfDates", testOfDates),
|
||||
("testDates", testDates),
|
||||
("testDistant", testDistant),
|
||||
("testNever", testNever),
|
||||
("testConcat", testConcat),
|
||||
("testMerge", testMerge),
|
||||
("testAt", testAt),
|
||||
("testFirst", testFirst),
|
||||
("testUntil", testUntil),
|
||||
("testNow", testNow),
|
||||
("testAt", testAt),
|
||||
("testAfterAndRepeating", testAfterAndRepeating),
|
||||
("testEveryPeriod", testEveryPeriod),
|
||||
("testEveryWeekday", testEveryWeekday),
|
||||
("testEveryMonthday", testEveryMonthday),
|
||||
("testPassingEmptyArrays", testPassingEmptyArrays),
|
||||
("testIntervalOffset", testIntervalOffset),
|
||||
("testNegativeIntervalOffset", testNegativeIntervalOffset),
|
||||
("testNilIntervalOffset", testNilIntervalOffset)
|
||||
("testOffset", testOffset)
|
||||
]
|
||||
}
|
||||
|
|
|
@ -19,50 +19,58 @@ final class TaskCenterTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testAdd() {
|
||||
let c = TaskCenter()
|
||||
|
||||
let task = makeTask()
|
||||
XCTAssertEqual(center.allTasks.count, 1)
|
||||
|
||||
let c = TaskCenter()
|
||||
c.add(task)
|
||||
|
||||
XCTAssertEqual(center.allTasks.count, 0)
|
||||
XCTAssertEqual(c.allTasks.count, 1)
|
||||
|
||||
c.add(task)
|
||||
XCTAssertEqual(c.allTasks.count, 1)
|
||||
center.add(task)
|
||||
XCTAssertEqual(center.allTasks.count, 1)
|
||||
XCTAssertEqual(c.allTasks.count, 0)
|
||||
|
||||
center.removeAll()
|
||||
}
|
||||
|
||||
func testRemove() {
|
||||
let task = makeTask()
|
||||
let tag = UUID().uuidString
|
||||
center.addTag(tag, to: task)
|
||||
|
||||
center.remove(task)
|
||||
|
||||
XCTAssertFalse(center.allTasks.contains(task))
|
||||
XCTAssertFalse(center.allTags.contains(tag))
|
||||
|
||||
center.removeAll()
|
||||
}
|
||||
|
||||
func testTag() {
|
||||
let task = makeTask()
|
||||
|
||||
let tag = UUID().uuidString
|
||||
|
||||
center.addTag(tag, to: task)
|
||||
XCTAssertTrue(center.tasksForTag(tag).contains(task))
|
||||
XCTAssertTrue(center.tagsForTask(task).contains(tag))
|
||||
XCTAssertTrue(center.tasks(forTag: tag).contains(task))
|
||||
XCTAssertTrue(center.tags(forTask: task).contains(tag))
|
||||
|
||||
center.removeTag(tag, from: task)
|
||||
XCTAssertFalse(center.tasksForTag(tag).contains(task))
|
||||
XCTAssertFalse(center.tagsForTask(task).contains(tag))
|
||||
XCTAssertFalse(center.tasks(forTag: tag).contains(task))
|
||||
XCTAssertFalse(center.tags(forTask: task).contains(tag))
|
||||
|
||||
center.removeAll()
|
||||
}
|
||||
|
||||
func testAll() {
|
||||
let task = makeTask()
|
||||
let tag1 = UUID().uuidString
|
||||
let tag2 = UUID().uuidString
|
||||
|
||||
let tag = UUID().uuidString
|
||||
center.addTags([tag1, tag2], to: task)
|
||||
|
||||
center.addTag(tag, to: task)
|
||||
XCTAssertEqual(center.allTags, [tag])
|
||||
XCTAssertEqual(center.allTags.sorted(), [tag1, tag2].sorted())
|
||||
XCTAssertEqual(center.allTasks, [task])
|
||||
|
||||
center.removeAll()
|
||||
|
@ -70,18 +78,17 @@ final class TaskCenterTests: XCTestCase {
|
|||
|
||||
func testOperation() {
|
||||
let task = makeTask()
|
||||
|
||||
let tag = UUID().uuidString
|
||||
|
||||
center.addTag(tag, to: task)
|
||||
|
||||
center.suspendByTag(tag)
|
||||
XCTAssertEqual(task.suspensions, 1)
|
||||
center.suspend(byTag: tag)
|
||||
XCTAssertEqual(task.suspensionCount, 1)
|
||||
|
||||
center.resumeByTag(tag)
|
||||
XCTAssertEqual(task.suspensions, 0)
|
||||
center.resume(byTag: tag)
|
||||
XCTAssertEqual(task.suspensionCount, 0)
|
||||
|
||||
center.cancelByTag(tag)
|
||||
center.cancel(byTag: tag)
|
||||
XCTAssertTrue(task.isCancelled)
|
||||
|
||||
center.removeAll()
|
||||
|
@ -89,7 +96,9 @@ final class TaskCenterTests: XCTestCase {
|
|||
|
||||
func testWeak() {
|
||||
let block = {
|
||||
_ = self.makeTask()
|
||||
let task = self.makeTask()
|
||||
XCTAssertEqual(self.center.allTasks.count, 1)
|
||||
_ = task
|
||||
}
|
||||
block()
|
||||
|
||||
|
|
|
@ -3,45 +3,83 @@ import XCTest
|
|||
|
||||
final class TaskTests: XCTestCase {
|
||||
|
||||
let leeway = 0.01.second
|
||||
|
||||
func testMetrics() {
|
||||
let e = expectation(description: "testMetrics")
|
||||
let date = Date()
|
||||
|
||||
let task = Plan.after(0.01.second, repeating: 0.01.second).do(queue: .global()) {
|
||||
e.fulfill()
|
||||
}
|
||||
XCTAssertTrue(task.creationDate.interval(since: date).isAlmostEqual(to: 0.second, leeway: leeway))
|
||||
|
||||
waitForExpectations(timeout: 0.1)
|
||||
|
||||
XCTAssertNotNil(task.firstExecutionDate)
|
||||
XCTAssertTrue(task.firstExecutionDate!.interval(since: date).isAlmostEqual(to: 0.01.second, leeway: leeway))
|
||||
|
||||
XCTAssertNotNil(task.lastExecutionDate)
|
||||
XCTAssertTrue(task.lastExecutionDate!.interval(since: date).isAlmostEqual(to: 0.01.second, leeway: leeway))
|
||||
}
|
||||
|
||||
func testAfter() {
|
||||
let e = expectation(description: "testSchedule")
|
||||
let date = Date()
|
||||
let task = Plan.after(0.1.second).do {
|
||||
XCTAssertTrue(Date().timeIntervalSince(date).isAlmostEqual(to: 0.1, leeway: 0.1))
|
||||
let task = Plan.after(0.01.second).do(queue: .global()) {
|
||||
XCTAssertTrue(Date().interval(since: date).isAlmostEqual(to: 0.01.second, leeway: self.leeway))
|
||||
e.fulfill()
|
||||
}
|
||||
waitForExpectations(timeout: 0.5)
|
||||
task.cancel()
|
||||
waitForExpectations(timeout: 0.1)
|
||||
|
||||
_ = task
|
||||
}
|
||||
|
||||
func testRepeat() {
|
||||
let e = expectation(description: "testRepeat")
|
||||
var t = 0
|
||||
let task = Plan.every(0.1.second).first(3).do {
|
||||
t += 1
|
||||
if t == 3 { e.fulfill() }
|
||||
var count = 0
|
||||
let task = Plan.every(0.01.second).first(3).do(queue: .global()) {
|
||||
count += 1
|
||||
if count == 3 { e.fulfill() }
|
||||
}
|
||||
waitForExpectations(timeout: 1)
|
||||
task.cancel()
|
||||
waitForExpectations(timeout: 0.1)
|
||||
|
||||
_ = task
|
||||
}
|
||||
|
||||
func testTaskCenter() {
|
||||
let task = Plan.never.do { }
|
||||
XCTAssertTrue(task.taskCenter === TaskCenter.default)
|
||||
|
||||
task.removeFromTaskCenter(TaskCenter())
|
||||
XCTAssertNotNil(task.taskCenter)
|
||||
|
||||
task.removeFromTaskCenter(task.taskCenter!)
|
||||
XCTAssertNil(task.taskCenter)
|
||||
|
||||
let center = TaskCenter()
|
||||
task.addToTaskCenter(center)
|
||||
XCTAssertTrue(task.taskCenter === center)
|
||||
}
|
||||
|
||||
func testDispatchQueue() {
|
||||
let e = expectation(description: "testQueue")
|
||||
let queue = DispatchQueue(label: "testQueue")
|
||||
let q = DispatchQueue(label: UUID().uuidString)
|
||||
|
||||
let task = Plan.after(0.1.second).do(queue: queue) {
|
||||
XCTAssertTrue(DispatchQueue.is(queue))
|
||||
let task = Plan.after(0.01.second).do(queue: q) {
|
||||
XCTAssertTrue(DispatchQueue.is(q))
|
||||
e.fulfill()
|
||||
}
|
||||
waitForExpectations(timeout: 0.5)
|
||||
task.cancel()
|
||||
waitForExpectations(timeout: 0.1)
|
||||
|
||||
_ = task
|
||||
}
|
||||
|
||||
func testThread() {
|
||||
let e = expectation(description: "testThread")
|
||||
DispatchQueue.global().async {
|
||||
let thread = Thread.current
|
||||
let task = Plan.after(0.1.second).do { task in
|
||||
let task = Plan.after(0.01.second).do { task in
|
||||
XCTAssertTrue(thread === Thread.current)
|
||||
e.fulfill()
|
||||
task.cancel()
|
||||
|
@ -49,18 +87,68 @@ final class TaskTests: XCTestCase {
|
|||
_ = task
|
||||
RunLoop.current.run()
|
||||
}
|
||||
waitForExpectations(timeout: 0.5)
|
||||
waitForExpectations(timeout: 0.1)
|
||||
}
|
||||
|
||||
func testSuspendResume() {
|
||||
let task1 = Plan.distantFuture.do { }
|
||||
XCTAssertEqual(task1.suspensions, 0)
|
||||
task1.suspend()
|
||||
task1.suspend()
|
||||
task1.suspend()
|
||||
XCTAssertEqual(task1.suspensions, 3)
|
||||
task1.resume()
|
||||
XCTAssertEqual(task1.suspensions, 2)
|
||||
let task = Plan.never.do { }
|
||||
XCTAssertEqual(task.suspensionCount, 0)
|
||||
task.suspend()
|
||||
task.suspend()
|
||||
task.suspend()
|
||||
XCTAssertEqual(task.suspensionCount, 3)
|
||||
task.resume()
|
||||
XCTAssertEqual(task.suspensionCount, 2)
|
||||
}
|
||||
|
||||
func testCancel() {
|
||||
let task = Plan.never.do { }
|
||||
XCTAssertFalse(task.isCancelled)
|
||||
task.cancel()
|
||||
XCTAssertTrue(task.isCancelled)
|
||||
}
|
||||
|
||||
func testExecuteNow() {
|
||||
let e = expectation(description: "testExecuteNow")
|
||||
let task = Plan.never.do {
|
||||
e.fulfill()
|
||||
}
|
||||
task.execute()
|
||||
waitForExpectations(timeout: 0.1)
|
||||
}
|
||||
|
||||
func testHost() {
|
||||
let e = expectation(description: "testHost")
|
||||
let fn = {
|
||||
let obj = NSObject()
|
||||
let task = Plan.after(0.1.second).do(queue: .main, block: {
|
||||
XCTFail("should never come here")
|
||||
})
|
||||
task.host(to: obj)
|
||||
}
|
||||
fn()
|
||||
DispatchQueue.main.async(after: 0.2.seconds) {
|
||||
e.fulfill()
|
||||
}
|
||||
waitForExpectations(timeout: 1)
|
||||
}
|
||||
|
||||
func testReschedule() {
|
||||
let e = expectation(description: "testReschedule")
|
||||
var i = 0
|
||||
let task = Plan.after(0.01.second).do(queue: .global()) { (task) in
|
||||
i += 1
|
||||
if task.executionCount == 3 && task.estimatedNextExecutionDate == nil {
|
||||
e.fulfill()
|
||||
}
|
||||
if task.executionCount > 3 {
|
||||
XCTFail("should never come here")
|
||||
}
|
||||
}
|
||||
DispatchQueue.global().async(after: 0.02.second) {
|
||||
task.reschedule(Plan.every(0.01.second).first(2))
|
||||
}
|
||||
waitForExpectations(timeout: 1)
|
||||
}
|
||||
|
||||
func testAddAndRemoveActions() {
|
||||
|
@ -83,69 +171,17 @@ final class TaskTests: XCTestCase {
|
|||
XCTAssertEqual(task.countOfActions, 0)
|
||||
}
|
||||
|
||||
func testReschedule() {
|
||||
let e = expectation(description: "testReschedule")
|
||||
var i = 0
|
||||
let task = Plan.after(0.1.second).do { (task) in
|
||||
i += 1
|
||||
if task.countOfExecutions == 6 && task.timeline.estimatedNextExecution == nil {
|
||||
e.fulfill()
|
||||
}
|
||||
if task.countOfExecutions > 6 {
|
||||
XCTFail("should never come here")
|
||||
}
|
||||
}
|
||||
DispatchQueue.global().async(after: 0.5.second) {
|
||||
task.reschedule(Plan.every(0.1.second).first(5))
|
||||
}
|
||||
waitForExpectations(timeout: 2)
|
||||
task.cancel()
|
||||
}
|
||||
|
||||
func testHost() {
|
||||
let e = expectation(description: "testHost")
|
||||
let fn = {
|
||||
let obj = NSObject()
|
||||
let task = Plan.after(0.1.second).do(queue: .main, onElapse: {
|
||||
XCTFail("should never come here")
|
||||
})
|
||||
task.host(on: obj)
|
||||
}
|
||||
fn()
|
||||
DispatchQueue.main.async(after: 0.2.seconds) {
|
||||
e.fulfill()
|
||||
}
|
||||
waitForExpectations(timeout: 1)
|
||||
}
|
||||
|
||||
func testLifetime() {
|
||||
let e = expectation(description: "testLifetime")
|
||||
let task = Plan.after(1.hour).do { }
|
||||
task.setLifetime(1.second)
|
||||
XCTAssertEqual(task.lifetime, 1.second)
|
||||
|
||||
DispatchQueue.global().async(after: 0.5.second) {
|
||||
XCTAssertTrue(task.restOfLifetime.isAlmostEqual(to: 0.5.second, leeway: 0.1.second))
|
||||
task.subtractLifetime(-0.5.second)
|
||||
}
|
||||
DispatchQueue.global().async(after: 1.second) {
|
||||
XCTAssertFalse(task.isCancelled)
|
||||
}
|
||||
DispatchQueue.global().async(after: 2.second) {
|
||||
XCTAssertTrue(task.isCancelled)
|
||||
e.fulfill()
|
||||
}
|
||||
waitForExpectations(timeout: 3)
|
||||
}
|
||||
|
||||
static var allTests = [
|
||||
("testAfter", testAfter),
|
||||
("testRepeat", testRepeat),
|
||||
("testTaskCenter", testTaskCenter),
|
||||
("testDispatchQueue", testDispatchQueue),
|
||||
("testThread", testThread),
|
||||
("testAddAndRemoveActions", testAddAndRemoveActions),
|
||||
("testReschedule", testReschedule),
|
||||
("testSuspendResume", testSuspendResume),
|
||||
("testCancel", testCancel),
|
||||
("testExecuteNow", testExecuteNow),
|
||||
("testHost", testHost),
|
||||
("testLifetime", testLifetime)
|
||||
("testReschedule", testReschedule),
|
||||
("testAddAndRemoveActions", testAddAndRemoveActions)
|
||||
]
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ final class WeekdayTests: XCTestCase {
|
|||
func testIs() {
|
||||
// ! Be careful the time zone problem.
|
||||
let d = Date(year: 2019, month: 1, day: 1)
|
||||
XCTAssertTrue(d.is(.tuesday))
|
||||
XCTAssertTrue(d.is(.tuesday, in: TimeZone.shanghai))
|
||||
}
|
||||
|
||||
func testAsDateComponents() {
|
||||
|
|
Loading…
Reference in New Issue