Remove Objc dependency, and improve documents.
This commit is contained in:
parent
6ac74e786c
commit
f48758d4f7
|
@ -21,8 +21,15 @@
|
|||
/* End PBXAggregateTarget section */
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
62661F412108433400055501 /* WeakSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62661F402108433300055501 /* WeakSet.swift */; };
|
||||
62EF93FA2108530D001F7A47 /* WeakSetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EF93F821085309001F7A47 /* WeakSetTests.swift */; };
|
||||
62EF93FF21086420001F7A47 /* TaskCenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EF93FD21086411001F7A47 /* TaskCenterTests.swift */; };
|
||||
62EF940821086F63001F7A47 /* Timeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62EF940721086F63001F7A47 /* Timeline.swift */; };
|
||||
6624104A2104A42C00013B00 /* Bucket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662410492104A42C00013B00 /* Bucket.swift */; };
|
||||
6624104E2104AF2100013B00 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6624104D2104AF2100013B00 /* Extensions.swift */; };
|
||||
6624105421075FDC00013B00 /* ExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6624105221075F8A00013B00 /* ExtensionsTests.swift */; };
|
||||
66241056210761F000013B00 /* BucketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66241055210761F000013B00 /* BucketTests.swift */; };
|
||||
662410592107804400013B00 /* Lock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662410582107804400013B00 /* Lock.swift */; };
|
||||
669D215020FE1B7300AFFDF7 /* Interval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669D214F20FE1B7300AFFDF7 /* Interval.swift */; };
|
||||
669D215220FE1B8A00AFFDF7 /* Time.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669D215120FE1B8A00AFFDF7 /* Time.swift */; };
|
||||
669D215420FE1BA600AFFDF7 /* Period.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669D215320FE1BA600AFFDF7 /* Period.swift */; };
|
||||
|
@ -58,9 +65,16 @@
|
|||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
62661F402108433300055501 /* WeakSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakSet.swift; sourceTree = "<group>"; };
|
||||
62EF93F821085309001F7A47 /* WeakSetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakSetTests.swift; sourceTree = "<group>"; };
|
||||
62EF93FD21086411001F7A47 /* TaskCenterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskCenterTests.swift; sourceTree = "<group>"; };
|
||||
62EF940721086F63001F7A47 /* Timeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timeline.swift; sourceTree = "<group>"; };
|
||||
662410492104A42C00013B00 /* Bucket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bucket.swift; sourceTree = "<group>"; };
|
||||
6624104D2104AF2100013B00 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
|
||||
66241051210617CD00013B00 /* Schedule.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = Schedule.podspec; sourceTree = "<group>"; };
|
||||
6624105221075F8A00013B00 /* ExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionsTests.swift; sourceTree = "<group>"; };
|
||||
66241055210761F000013B00 /* BucketTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BucketTests.swift; sourceTree = "<group>"; };
|
||||
662410582107804400013B00 /* Lock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Lock.swift; sourceTree = "<group>"; };
|
||||
669D214F20FE1B7300AFFDF7 /* Interval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interval.swift; sourceTree = "<group>"; };
|
||||
669D215120FE1B8A00AFFDF7 /* Time.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Time.swift; sourceTree = "<group>"; };
|
||||
669D215320FE1BA600AFFDF7 /* Period.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Period.swift; sourceTree = "<group>"; };
|
||||
|
@ -99,11 +113,31 @@
|
|||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
62EF93FB21085312001F7A47 /* Utils */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6624105221075F8A00013B00 /* ExtensionsTests.swift */,
|
||||
66241055210761F000013B00 /* BucketTests.swift */,
|
||||
62EF93F821085309001F7A47 /* WeakSetTests.swift */,
|
||||
);
|
||||
name = Utils;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
62EF93FC210863FA001F7A47 /* DateTime */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
OBJ_15 /* DateTimeTests.swift */,
|
||||
);
|
||||
name = DateTime;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
662410462104A0EC00013B00 /* Utils */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
662410492104A42C00013B00 /* Bucket.swift */,
|
||||
6624104D2104AF2100013B00 /* Extensions.swift */,
|
||||
662410582107804400013B00 /* Lock.swift */,
|
||||
62661F402108433300055501 /* WeakSet.swift */,
|
||||
);
|
||||
name = Utils;
|
||||
sourceTree = "<group>";
|
||||
|
@ -140,10 +174,12 @@
|
|||
OBJ_14 /* ScheduleTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
OBJ_15 /* DateTimeTests.swift */,
|
||||
62EF93FC210863FA001F7A47 /* DateTime */,
|
||||
62EF93FB21085312001F7A47 /* Utils */,
|
||||
OBJ_16 /* ScheduleTests.swift */,
|
||||
OBJ_17 /* Util.swift */,
|
||||
OBJ_18 /* XCTestManifests.swift */,
|
||||
62EF93FD21086411001F7A47 /* TaskCenterTests.swift */,
|
||||
);
|
||||
name = ScheduleTests;
|
||||
path = Tests/ScheduleTests;
|
||||
|
@ -185,6 +221,7 @@
|
|||
OBJ_10 /* Task.swift */,
|
||||
669D215A20FE1C0E00AFFDF7 /* TaskCenter.swift */,
|
||||
OBJ_11 /* Schedule.swift */,
|
||||
62EF940721086F63001F7A47 /* Timeline.swift */,
|
||||
662410462104A0EC00013B00 /* Utils */,
|
||||
);
|
||||
name = Schedule;
|
||||
|
@ -277,13 +314,16 @@
|
|||
669D215220FE1B8A00AFFDF7 /* Time.swift in Sources */,
|
||||
669D215020FE1B7300AFFDF7 /* Interval.swift in Sources */,
|
||||
669D215620FE1BB400AFFDF7 /* Weekday.swift in Sources */,
|
||||
62EF940821086F63001F7A47 /* Timeline.swift in Sources */,
|
||||
OBJ_28 /* Task.swift in Sources */,
|
||||
OBJ_29 /* Schedule.swift in Sources */,
|
||||
62661F412108433400055501 /* WeakSet.swift in Sources */,
|
||||
669D215820FE1BC000AFFDF7 /* Monthday.swift in Sources */,
|
||||
669D215B20FE1C0E00AFFDF7 /* TaskCenter.swift in Sources */,
|
||||
669D215D20FE1D1800AFFDF7 /* ParasiticTask.swift in Sources */,
|
||||
6624104E2104AF2100013B00 /* Extensions.swift in Sources */,
|
||||
6624104A2104A42C00013B00 /* Bucket.swift in Sources */,
|
||||
662410592107804400013B00 /* Lock.swift in Sources */,
|
||||
669D215420FE1BA600AFFDF7 /* Period.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -300,10 +340,14 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 0;
|
||||
files = (
|
||||
6624105421075FDC00013B00 /* ExtensionsTests.swift in Sources */,
|
||||
62EF93FA2108530D001F7A47 /* WeakSetTests.swift in Sources */,
|
||||
OBJ_48 /* DateTimeTests.swift in Sources */,
|
||||
OBJ_49 /* ScheduleTests.swift in Sources */,
|
||||
OBJ_50 /* Util.swift in Sources */,
|
||||
OBJ_51 /* XCTestManifests.swift in Sources */,
|
||||
66241056210761F000013B00 /* BucketTests.swift in Sources */,
|
||||
62EF93FF21086420001F7A47 /* TaskCenterTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -24,36 +24,50 @@ struct Bucket<Element> {
|
|||
|
||||
private var nextKey = BucketKey(rawValue: 0)
|
||||
|
||||
private var elements: [BucketKey: Element] = [:]
|
||||
typealias Entry = (key: BucketKey, element: Element)
|
||||
private var entries: [Entry] = []
|
||||
|
||||
@discardableResult
|
||||
mutating func insert(_ new: Element) -> BucketKey {
|
||||
mutating func add(_ new: Element) -> BucketKey {
|
||||
let key = nextKey
|
||||
nextKey = BucketKey(rawValue: nextKey.rawValue &+ 1)
|
||||
elements[key] = new
|
||||
entries.append((key: key, element: new))
|
||||
return key
|
||||
}
|
||||
|
||||
func element(for key: BucketKey) -> Element? {
|
||||
for entry in entries {
|
||||
if entry.key == key {
|
||||
return entry.element
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
mutating func removeElement(byKey key: BucketKey) -> Element? {
|
||||
return elements.removeValue(forKey: key)
|
||||
mutating func removeElement(for key: BucketKey) -> Element? {
|
||||
for i in 0..<entries.count {
|
||||
if entries[i].key == key {
|
||||
let element = entries[i].element
|
||||
entries.remove(at: i)
|
||||
return element
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
mutating func removeAll() {
|
||||
elements.removeAll()
|
||||
entries.removeAll()
|
||||
}
|
||||
|
||||
static var empty: Bucket {
|
||||
return Bucket()
|
||||
var count: Int {
|
||||
return entries.count
|
||||
}
|
||||
}
|
||||
|
||||
extension Bucket: Sequence {
|
||||
|
||||
func makeIterator() -> AnyIterator<Element> {
|
||||
var iterator = elements.makeIterator()
|
||||
return AnyIterator {
|
||||
iterator.next()?.value
|
||||
}
|
||||
return AnyIterator(entries.map({ $0.element }).makeIterator())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,30 +7,24 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
extension FixedWidthInteger {
|
||||
extension Int {
|
||||
|
||||
func clampedAdding(_ other: Self) -> Self {
|
||||
func clampedAdding(_ other: Int) -> Int {
|
||||
let r = addingReportingOverflow(other)
|
||||
return r.overflow ? (other > 0 ? .max : .min) : r.partialValue
|
||||
}
|
||||
|
||||
func clampedSubtracting(_ other: Self) -> Self {
|
||||
func clampedSubtracting(_ other: Int) -> Int {
|
||||
let r = subtractingReportingOverflow(other)
|
||||
return r.overflow ? (other > 0 ? .max : .min) : r.partialValue
|
||||
return r.overflow ? (other > 0 ? .min : .max) : r.partialValue
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
extension Double {
|
||||
|
||||
func matches(pattern: String) -> [String] {
|
||||
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {
|
||||
return []
|
||||
}
|
||||
let matches = regex.matches(in: self, options: [], range: NSRange(location: 0, length: count))
|
||||
guard let match = matches.first else { return [] }
|
||||
|
||||
return (0..<match.numberOfRanges).reduce(into: [String]()) { (r, i) in
|
||||
r.append((self as NSString).substring(with: match.range(at: i)))
|
||||
}
|
||||
func clampedToInt() -> Int {
|
||||
if self > Double(Int.max) { return Int.max }
|
||||
if self < Double(Int.min) { return Int.min }
|
||||
return Int(self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,15 +7,22 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
/// `Interval` represents a duration of time.
|
||||
/// `Interval` represents a length of time.
|
||||
///
|
||||
/// The value range of interval is [Int.min.nanoseconds...Int.max.nanoseconds],
|
||||
/// that is, about -292.years ~ 292.years, enough for us!
|
||||
public struct Interval {
|
||||
|
||||
/// The length of this interval, measured in nanoseconds.
|
||||
public let nanoseconds: Double
|
||||
let ns: Int
|
||||
|
||||
/// The length of this interval in nanoseconds.
|
||||
public var nanoseconds: Double {
|
||||
return Double(ns)
|
||||
}
|
||||
|
||||
/// Creates an interval from the given number of nanoseconds.
|
||||
public init(nanoseconds: Double) {
|
||||
self.nanoseconds = nanoseconds
|
||||
self.ns = nanoseconds.clampedToInt()
|
||||
}
|
||||
|
||||
/// A boolean value indicating whether this interval is negative.
|
||||
|
@ -26,27 +33,31 @@ public struct Interval {
|
|||
/// but the interval between 7:00 and 6:00 is `-1.hour`.
|
||||
/// In this case, `-1.hour` means **one hour ago**.
|
||||
///
|
||||
/// - The interval comparing `3.hour` and `1.hour` is `2.hour`,
|
||||
/// but the interval comparing `1.hour` and `3.hour` is `-2.hour`.
|
||||
/// - The interval comparing `3.hour` to `1.hour` is `2.hour`,
|
||||
/// but the interval comparing `1.hour` to `3.hour` is `-2.hour`.
|
||||
/// In this case, `-2.hour` means **two hours shorter**
|
||||
public var isNegative: Bool {
|
||||
return nanoseconds.isLess(than: 0)
|
||||
return ns < 0
|
||||
}
|
||||
|
||||
/// The magnitude of this interval.
|
||||
///
|
||||
/// It's the absolute value of the length of this interval,
|
||||
/// The absolute value of the length of this interval,
|
||||
/// measured in nanoseconds, but disregarding its sign.
|
||||
public var magnitude: Double {
|
||||
return nanoseconds.magnitude
|
||||
public var magnitude: UInt {
|
||||
return ns.magnitude
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension Interval {
|
||||
|
||||
/// Returns a boolean value indicating whether this interval is longer than the given value.
|
||||
/// Returns a boolean value indicating whether this interval is longer
|
||||
/// than the given value.
|
||||
public func isLonger(than other: Interval) -> Bool {
|
||||
return magnitude > other.magnitude
|
||||
}
|
||||
|
||||
/// Returns a boolean value indicating whether this interval is shorter than the given value.
|
||||
/// Returns a boolean value indicating whether this interval is shorter
|
||||
/// than the given value.
|
||||
public func isShorter(than other: Interval) -> Bool {
|
||||
return magnitude < other.magnitude
|
||||
}
|
||||
|
@ -87,30 +98,30 @@ extension Interval {
|
|||
|
||||
/// Creates an interval from the given number of seconds.
|
||||
public init(seconds: Double) {
|
||||
self.nanoseconds = seconds * pow(10, 9)
|
||||
self.init(nanoseconds: seconds * pow(10, 9))
|
||||
}
|
||||
|
||||
/// The length of this interval, measured in seconds.
|
||||
/// The length of this interval in seconds.
|
||||
public var seconds: Double {
|
||||
return nanoseconds / pow(10, 9)
|
||||
}
|
||||
|
||||
/// The length of this interval, measured in minutes.
|
||||
/// The length of this interval in minutes.
|
||||
public var minutes: Double {
|
||||
return seconds / 60
|
||||
}
|
||||
|
||||
/// The length of this interval, measured in hours.
|
||||
/// The length of this interval in hours.
|
||||
public var hours: Double {
|
||||
return minutes / 60
|
||||
}
|
||||
|
||||
/// The length of this interval, measured in days.
|
||||
/// The length of this interval in days.
|
||||
public var days: Double {
|
||||
return hours / 24
|
||||
}
|
||||
|
||||
/// The length of this interval, measured in weeks.
|
||||
/// The length of this interval in weeks.
|
||||
public var weeks: Double {
|
||||
return days / 7
|
||||
}
|
||||
|
@ -131,53 +142,48 @@ extension Interval: Hashable {
|
|||
|
||||
extension Date {
|
||||
|
||||
/// The interval between this date and now.
|
||||
/// The interval between this date and the current date and time.
|
||||
///
|
||||
/// If the date is earlier than now, the interval is negative.
|
||||
/// If this date is earlier than now, the interval will be negative.
|
||||
public var intervalSinceNow: Interval {
|
||||
return timeIntervalSinceNow.seconds
|
||||
}
|
||||
|
||||
/// Returns the interval between this date and the given date.
|
||||
///
|
||||
/// If the date is earlier than the given date, the interval is negative.
|
||||
/// If this date is earlier than the given date, the interval will be negative.
|
||||
public func interval(since date: Date) -> Interval {
|
||||
return timeIntervalSince(date).seconds
|
||||
}
|
||||
|
||||
/// Returns a new date by adding an interval to the date.
|
||||
/// Returns a new date by adding an interval to this date.
|
||||
public func addingInterval(_ interval: Interval) -> Date {
|
||||
return addingTimeInterval(interval.seconds)
|
||||
}
|
||||
|
||||
/// Returns a new date by adding an interval to the date.
|
||||
/// Returns a date with an interval added to it.
|
||||
public static func +(lhs: Date, rhs: Interval) -> Date {
|
||||
return lhs.addingInterval(rhs)
|
||||
}
|
||||
|
||||
/// Adds an interval to the date.
|
||||
/// Adds a interval to the date.
|
||||
public static func +=(lhs: inout Date, rhs: Interval) {
|
||||
lhs = lhs + rhs
|
||||
}
|
||||
}
|
||||
|
||||
extension Interval {
|
||||
|
||||
var ns: Int {
|
||||
if nanoseconds > Double(Int.max) { return .max }
|
||||
if nanoseconds < Double(Int.min) { return .min }
|
||||
return Int(nanoseconds)
|
||||
}
|
||||
}
|
||||
|
||||
extension DispatchSourceTimer {
|
||||
|
||||
func schedule(after interval: Interval) {
|
||||
guard !interval.isNegative else {
|
||||
schedule(wallDeadline: .distantFuture)
|
||||
return
|
||||
}
|
||||
schedule(wallDeadline: .now() + DispatchTimeInterval.nanoseconds(interval.ns))
|
||||
}
|
||||
}
|
||||
|
||||
/// `IntervalConvertible` provides a set of intuitive apis for creating interval.
|
||||
/// `IntervalConvertible` provides a set of intuitive api for creating interval.
|
||||
public protocol IntervalConvertible {
|
||||
|
||||
var nanoseconds: Interval { get }
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// Lock.swift
|
||||
// Schedule
|
||||
//
|
||||
// Created by Quentin Jin on 2018/7/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class Lock {
|
||||
|
||||
private let mutex = UnsafeMutablePointer<pthread_mutex_t>.allocate(capacity: 1)
|
||||
|
||||
init() {
|
||||
let err = pthread_mutex_init(mutex, nil)
|
||||
precondition(err == 0)
|
||||
}
|
||||
|
||||
deinit {
|
||||
let err = pthread_mutex_destroy(mutex)
|
||||
precondition(err == 0)
|
||||
mutex.deallocate()
|
||||
}
|
||||
|
||||
func lock() {
|
||||
let err = pthread_mutex_lock(mutex)
|
||||
precondition(err == 0)
|
||||
}
|
||||
|
||||
func unlock() {
|
||||
let err = pthread_mutex_unlock(mutex)
|
||||
precondition(err == 0)
|
||||
}
|
||||
}
|
||||
|
||||
extension Lock {
|
||||
|
||||
@inline(__always)
|
||||
func withLock<T>(_ body: () throws -> T) rethrows -> T {
|
||||
lock()
|
||||
defer { unlock() }
|
||||
return try body()
|
||||
}
|
||||
}
|
|
@ -16,8 +16,10 @@ import Foundation
|
|||
///
|
||||
/// If you add a period `1.month` to the 1st January,
|
||||
/// you will get the 1st February.
|
||||
///
|
||||
/// If you add the same period to the 1st February,
|
||||
/// you will get the 1st March.
|
||||
///
|
||||
/// But the intervals(`31.days` in case 1, `28.days` or `29.days` in case 2)
|
||||
/// in these two cases are quite different.
|
||||
public struct Period {
|
||||
|
@ -59,12 +61,12 @@ public struct Period {
|
|||
nanoseconds: lhs.nanoseconds.clampedAdding(rhs.nanoseconds))
|
||||
}
|
||||
|
||||
/// Returns a new date by adding the right period to the left date.
|
||||
/// Returns a date with a period added to it.
|
||||
public static func +(lhs: Date, rhs: Period) -> Date {
|
||||
return Calendar.autoupdatingCurrent.date(byAdding: rhs.asDateComponents(), to: lhs) ?? .distantFuture
|
||||
}
|
||||
|
||||
/// Returns a new period by adding the right interval to the left period.
|
||||
/// Return a period with a interval added to it.
|
||||
public static func +(lhs: Period, rhs: Interval) -> Period {
|
||||
return Period(years: lhs.years, months: lhs.months, days: lhs.days,
|
||||
hours: lhs.hours, minutes: lhs.minutes, seconds: lhs.seconds,
|
||||
|
@ -80,22 +82,18 @@ public struct Period {
|
|||
|
||||
extension Int {
|
||||
|
||||
/// Period by setting years to this value.
|
||||
public var years: Period {
|
||||
return Period(years: self)
|
||||
}
|
||||
|
||||
/// Period by setting years to this value.
|
||||
public var year: Period {
|
||||
return years
|
||||
}
|
||||
|
||||
/// Period by setting months to this value.
|
||||
public var months: Period {
|
||||
return Period(months: self)
|
||||
}
|
||||
|
||||
/// Period by setting months to this value.
|
||||
public var month: Period {
|
||||
return months
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
/// `ActionKey` represents a token that can be used to operate action.
|
||||
public protocol ActionKey {
|
||||
var underlying: UInt64 { get }
|
||||
}
|
||||
|
@ -17,127 +18,133 @@ extension BucketKey: ActionKey {
|
|||
}
|
||||
}
|
||||
|
||||
/// `Task` represents a series of actions to be scheduled.
|
||||
/// `Task` represents a job to be scheduled.
|
||||
public class Task {
|
||||
|
||||
/// The timstamp last time this task was scheduled at.
|
||||
public var lastSchedule: Date? {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return _lastSchedule
|
||||
}
|
||||
private var _lastSchedule: Date?
|
||||
private let _lock = Lock()
|
||||
|
||||
/// The timestamp next time this task will be scheduled at.
|
||||
public var nextSchedule: Date? {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return deadline
|
||||
}
|
||||
private var _iterator: AnyIterator<Interval>
|
||||
|
||||
/// All tags associate with this task.
|
||||
public var tags: Set<String> {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
return _tags
|
||||
}
|
||||
private var _tags: Set<String> = []
|
||||
|
||||
private lazy var lock = NSRecursiveLock()
|
||||
|
||||
private var iterator: AnyIterator<Interval>
|
||||
|
||||
private var deadline: Date!
|
||||
private var _timer: DispatchSourceTimer?
|
||||
|
||||
private typealias Action = (Task) -> Void
|
||||
private var actions = Bucket<Action>.empty
|
||||
|
||||
private var timer: DispatchSourceTimer!
|
||||
private lazy var _actions = Bucket<Action>()
|
||||
|
||||
private var suspensions: UInt64 = 0
|
||||
private lazy var _suspensions: UInt64 = 0
|
||||
private lazy var _timeline = Timeline()
|
||||
private lazy var _tags: Set<String> = []
|
||||
|
||||
init(schedule: Schedule,
|
||||
queue: DispatchQueue? = nil,
|
||||
tag: String? = nil,
|
||||
onElapse: @escaping (Task) -> Void) {
|
||||
|
||||
self.iterator = schedule.makeIterator()
|
||||
guard let interval = self.iterator.next() else {
|
||||
return
|
||||
}
|
||||
self.deadline = Date() + interval
|
||||
self.actions.insert(onElapse)
|
||||
self.timer = DispatchSource.makeTimerSource(queue: queue)
|
||||
self.timer.setEventHandler { [weak self] in
|
||||
self._iterator = schedule.makeIterator()
|
||||
|
||||
guard let interval = self._iterator.next() else { return }
|
||||
|
||||
self._timer = DispatchSource.makeTimerSource(queue: queue)
|
||||
|
||||
self._actions.add(onElapse)
|
||||
self._timer?.setEventHandler { [weak self] in
|
||||
self?.elapse()
|
||||
}
|
||||
self.timer.schedule(after: interval)
|
||||
self.timer.resume()
|
||||
self._timer?.schedule(after: interval)
|
||||
|
||||
let now = Date()
|
||||
self._timeline.activate = now
|
||||
self._timeline.nextSchedule = now.addingInterval(interval)
|
||||
TaskCenter.shared.add(self, withTag: tag)
|
||||
|
||||
self._timer?.resume()
|
||||
}
|
||||
|
||||
private func elapse() {
|
||||
_lock.lock()
|
||||
let now = Date()
|
||||
if _timeline.firstSchedule == nil {
|
||||
_timeline.firstSchedule = now
|
||||
}
|
||||
_timeline.lastSchedule = now
|
||||
|
||||
lock.lock()
|
||||
_lastSchedule = now
|
||||
guard let interval = iterator.next(), !interval.isNegative else {
|
||||
deadline = nil
|
||||
let _actions = actions
|
||||
lock.unlock()
|
||||
_actions.forEach { $0(self) }
|
||||
guard let interval = _iterator.next() else {
|
||||
_timeline.nextSchedule = nil
|
||||
let actions = _actions
|
||||
_lock.unlock()
|
||||
actions.forEach { $0(self) }
|
||||
return
|
||||
}
|
||||
deadline = deadline.addingInterval(interval)
|
||||
let _actions = actions
|
||||
timer.schedule(after: interval)
|
||||
lock.unlock()
|
||||
|
||||
_actions.forEach { $0(self) }
|
||||
_timeline.nextSchedule = _timeline.nextSchedule?.addingInterval(interval)
|
||||
|
||||
_timer?.schedule(after: (_timeline.nextSchedule ?? Date.distantFuture).interval(since: now))
|
||||
let actions = _actions
|
||||
_lock.unlock()
|
||||
|
||||
actions.forEach { $0(self) }
|
||||
}
|
||||
|
||||
/// The timeline of this task.
|
||||
public var timeline: Timeline {
|
||||
return _lock.withLock {
|
||||
_timeline
|
||||
}
|
||||
}
|
||||
|
||||
/// All tags associated with this task.
|
||||
public var tags: Set<String> {
|
||||
return _lock.withLock {
|
||||
_tags
|
||||
}
|
||||
}
|
||||
|
||||
/// Reschedules this task with the new schedule.
|
||||
public func reschedule(_ new: Schedule) {
|
||||
lock.lock()
|
||||
iterator = new.makeIterator()
|
||||
lock.unlock()
|
||||
_lock.withLock {
|
||||
_iterator = new.makeIterator()
|
||||
}
|
||||
}
|
||||
|
||||
/// Suspends this task.
|
||||
public func suspend() {
|
||||
lock.lock()
|
||||
if suspensions < UInt64.max {
|
||||
suspensions += 1
|
||||
timer.suspend()
|
||||
guard let timer = _timer else { return }
|
||||
_lock.withLock {
|
||||
if _suspensions < UInt64.max {
|
||||
timer.suspend()
|
||||
_suspensions += 1
|
||||
}
|
||||
}
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
/// Resumes this task.
|
||||
public func resume() {
|
||||
lock.lock()
|
||||
if suspensions > 0 {
|
||||
suspensions -= 1
|
||||
timer.resume()
|
||||
guard let timer = _timer else { return }
|
||||
_lock.withLock {
|
||||
if _suspensions > 0 {
|
||||
timer.resume()
|
||||
_suspensions -= 1
|
||||
}
|
||||
}
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
/// Cancels this task.
|
||||
public func cancel() {
|
||||
lock.lock()
|
||||
timer.cancel()
|
||||
lock.unlock()
|
||||
guard let timer = _timer else { return }
|
||||
_lock.withLock {
|
||||
timer.cancel()
|
||||
_timeline.cancel = Date()
|
||||
}
|
||||
TaskCenter.shared.remove(self)
|
||||
}
|
||||
|
||||
deinit {
|
||||
lock.lock()
|
||||
while suspensions > 0 {
|
||||
suspensions -= 1
|
||||
timer.resume()
|
||||
guard let timer = _timer else { return }
|
||||
_lock.withLock {
|
||||
while _suspensions > 0 {
|
||||
timer.resume()
|
||||
_suspensions -= 1
|
||||
}
|
||||
}
|
||||
lock.unlock()
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
@ -145,27 +152,26 @@ public class Task {
|
|||
|
||||
extension Task {
|
||||
|
||||
/// Adds an action to this task.
|
||||
/// Adds the action to this task.
|
||||
@discardableResult
|
||||
public func addAction(_ action: @escaping (Task) -> Void) -> ActionKey {
|
||||
lock.lock()
|
||||
let key = actions.insert(action)
|
||||
lock.unlock()
|
||||
return key
|
||||
return _lock.withLock {
|
||||
_actions.add(action)
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the key's corresponding action from this task.
|
||||
/// Removes action by key from this task.
|
||||
public func removeAction(byKey key: ActionKey) {
|
||||
lock.lock()
|
||||
actions.removeElement(byKey: BucketKey(rawValue: key.underlying))
|
||||
lock.unlock()
|
||||
_lock.withLock {
|
||||
_ = _actions.removeElement(for: BucketKey(rawValue: key.underlying))
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes all actions from this task.
|
||||
public func removeAllActions() {
|
||||
lock.lock()
|
||||
actions.removeAll()
|
||||
lock.unlock()
|
||||
_lock.withLock {
|
||||
_actions.removeAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,18 +179,18 @@ extension Task {
|
|||
|
||||
/// Adds tags to this task.
|
||||
public func addTags(_ tags: [String]) {
|
||||
let set = Set(tags)
|
||||
var set = Set(tags)
|
||||
|
||||
lock.lock()
|
||||
let intersection = set.intersection(self._tags)
|
||||
guard intersection.count > 0 else {
|
||||
lock.unlock()
|
||||
_lock.lock()
|
||||
set.subtract(_tags)
|
||||
guard set.count > 0 else {
|
||||
_lock.unlock()
|
||||
return
|
||||
}
|
||||
self._tags.formUnion(intersection)
|
||||
lock.unlock()
|
||||
_tags.formUnion(set)
|
||||
_lock.unlock()
|
||||
|
||||
for tag in intersection {
|
||||
for tag in set {
|
||||
TaskCenter.shared.add(tag: tag, to: self)
|
||||
}
|
||||
}
|
||||
|
@ -201,19 +207,17 @@ extension Task {
|
|||
|
||||
/// Removes tags from this task.
|
||||
public func removeTags(_ tags: [String]) {
|
||||
let set = Set(tags)
|
||||
lock.lock()
|
||||
let intersection = set.intersection(self._tags)
|
||||
guard intersection.count > 0 else {
|
||||
lock.unlock()
|
||||
var set = Set(tags)
|
||||
_lock.lock()
|
||||
set.formIntersection(_tags)
|
||||
guard set.count > 0 else {
|
||||
_lock.unlock()
|
||||
return
|
||||
}
|
||||
for tag in intersection {
|
||||
self._tags.insert(tag)
|
||||
}
|
||||
lock.unlock()
|
||||
_tags.subtract(set)
|
||||
_lock.unlock()
|
||||
|
||||
for tag in intersection {
|
||||
for tag in set {
|
||||
TaskCenter.shared.remove(tag: tag, from: self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,56 +13,53 @@ final class TaskCenter {
|
|||
|
||||
private init() { }
|
||||
|
||||
private var lock = NSLock()
|
||||
private var lock = Lock()
|
||||
|
||||
private var tasks: Set<Task> = []
|
||||
private var tags: [String: NSHashTable<Task>] = [:]
|
||||
private var registry: [String: WeakSet<Task>] = [:]
|
||||
|
||||
func add(_ task: Task, withTag tag: String? = nil) {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
|
||||
tasks.insert(task)
|
||||
|
||||
if let tag = tag {
|
||||
if tags[tag] == nil {
|
||||
tags[tag] = NSHashTable<Task>(options: .weakMemory)
|
||||
lock.withLock {
|
||||
tasks.insert(task)
|
||||
if let tag = tag {
|
||||
if registry[tag] == nil {
|
||||
registry[tag] = WeakSet()
|
||||
}
|
||||
registry[tag]?.add(task)
|
||||
}
|
||||
weak var t = task
|
||||
tags[tag]?.add(t)
|
||||
}
|
||||
}
|
||||
|
||||
func remove(_ task: Task) {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
|
||||
tasks.remove(task)
|
||||
lock.withLock {
|
||||
_ = tasks.remove(task)
|
||||
}
|
||||
}
|
||||
|
||||
func add(tag: String, to task: Task) {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
|
||||
if tags[tag] == nil {
|
||||
tags[tag] = NSHashTable<Task>(options: .weakMemory)
|
||||
lock.withLock {
|
||||
if registry[tag] == nil {
|
||||
registry[tag] = WeakSet()
|
||||
}
|
||||
registry[tag]?.add(task)
|
||||
}
|
||||
weak var t = task
|
||||
tags[tag]?.add(t)
|
||||
}
|
||||
|
||||
func remove(tag: String, from task: Task) {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
|
||||
tags[tag]?.remove(task)
|
||||
lock.withLock {
|
||||
_ = registry[tag]?.remove(task)
|
||||
}
|
||||
}
|
||||
|
||||
func tasks(forTag tag: String) -> [Task] {
|
||||
lock.lock()
|
||||
defer { lock.unlock() }
|
||||
|
||||
return tags[tag]?.allObjects ?? []
|
||||
return lock.withLock {
|
||||
registry[tag]?.objects ?? []
|
||||
}
|
||||
}
|
||||
|
||||
func contains(_ task: Task) -> Bool {
|
||||
return lock.withLock {
|
||||
tasks.contains(task)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,13 +10,13 @@ import Foundation
|
|||
/// `Time` represents a time without a date.
|
||||
public struct Time {
|
||||
|
||||
/// Hour of this time, max is 23, min is 0.
|
||||
/// Hour of this time.
|
||||
public let hour: Int
|
||||
|
||||
/// Minute of this time, max is 59, min is 0.
|
||||
/// Minute of this time.
|
||||
public let minute: Int
|
||||
|
||||
/// Second of this time, max is 59, min is 0.
|
||||
/// Second of this time.
|
||||
public let second: Int
|
||||
|
||||
/// Nanosecond of this time.
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// Timeline.swift
|
||||
// Schedule
|
||||
//
|
||||
// Created by Quentin Jin on 2018/7/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// `Timeline` records a task's schedule.
|
||||
public struct Timeline {
|
||||
|
||||
/// The time when the first time task was scheduled.
|
||||
public internal(set) var firstSchedule: Date?
|
||||
|
||||
/// The time when the last time task was scheduled.
|
||||
public internal(set) var lastSchedule: Date?
|
||||
|
||||
/// The time when the next time task will be scheduled.
|
||||
public internal(set) var nextSchedule: Date?
|
||||
|
||||
/// The time when task was activated.
|
||||
public internal(set) var activate: Date?
|
||||
|
||||
/// The time when task was canceled.
|
||||
public internal(set) var cancel: Date?
|
||||
|
||||
init() { }
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
//
|
||||
// WeakSet.swift
|
||||
// Schedule
|
||||
//
|
||||
// Created by Quentin Jin on 2018/7/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct WeakBox<T: AnyObject> {
|
||||
weak var underlying: T?
|
||||
init(_ value: T) {
|
||||
self.underlying = value
|
||||
}
|
||||
}
|
||||
|
||||
extension WeakBox: Hashable {
|
||||
|
||||
var hashValue: Int {
|
||||
guard let value = self.underlying else { return 0 }
|
||||
return ObjectIdentifier(value).hashValue
|
||||
}
|
||||
|
||||
static func == (lhs: WeakBox<T>, rhs: WeakBox<T>) -> Bool {
|
||||
return lhs.hashValue == rhs.hashValue
|
||||
}
|
||||
}
|
||||
|
||||
struct WeakSet<T: AnyObject> {
|
||||
|
||||
private var set = Set<WeakBox<T>>()
|
||||
|
||||
mutating func add(_ object: T) {
|
||||
self.set.insert(WeakBox(object))
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
mutating func remove(_ object: T) -> T? {
|
||||
return self.set.remove(WeakBox(object))?.underlying
|
||||
}
|
||||
|
||||
func contains(_ object: T) -> Bool {
|
||||
return set.contains(WeakBox(object))
|
||||
}
|
||||
|
||||
var objects: [T] {
|
||||
return self.set.map { $0.underlying }.compactMap { $0 }
|
||||
}
|
||||
|
||||
var count: Int {
|
||||
return self.objects.count
|
||||
}
|
||||
}
|
||||
|
||||
extension WeakSet: Sequence {
|
||||
|
||||
func makeIterator() -> AnyIterator<T> {
|
||||
return AnyIterator(objects.makeIterator())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// BucketTests.swift
|
||||
// ScheduleTests
|
||||
//
|
||||
// Created by Quentin Jin on 2018/7/24.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Schedule
|
||||
|
||||
final class BucketTests: XCTestCase {
|
||||
|
||||
typealias Fn = () -> Int
|
||||
|
||||
func testAdd() {
|
||||
var bucket = Bucket<Fn>()
|
||||
let key = bucket.add({ 1 })
|
||||
let element = bucket.element(for: key)
|
||||
XCTAssertNotNil(element)
|
||||
guard let fn = element else { return }
|
||||
XCTAssertEqual(fn(), 1)
|
||||
}
|
||||
|
||||
func testRemove() {
|
||||
var bucket = Bucket<Fn>()
|
||||
let k0 = bucket.add { 0 }
|
||||
bucket.add { 1 }
|
||||
bucket.add { 2 }
|
||||
XCTAssertEqual(bucket.count, 3)
|
||||
|
||||
let e0 = bucket.removeElement(for: k0)
|
||||
XCTAssertNotNil(e0)
|
||||
|
||||
guard let fn0 = e0 else { return }
|
||||
XCTAssertEqual(fn0(), 0)
|
||||
|
||||
XCTAssertEqual(bucket.count, 2)
|
||||
|
||||
bucket.removeAll()
|
||||
XCTAssertEqual(bucket.count, 0)
|
||||
}
|
||||
|
||||
func testSequence() {
|
||||
var bucket = Bucket<Fn>()
|
||||
bucket.add { 0 }
|
||||
bucket.add { 1 }
|
||||
bucket.add { 2 }
|
||||
|
||||
var i = 0
|
||||
for fn in bucket {
|
||||
XCTAssertEqual(fn(), i)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
static var allTests = [
|
||||
("testAdd", testAdd),
|
||||
("testRemove", testRemove),
|
||||
("testSequence", testSequence)
|
||||
]
|
||||
}
|
|
@ -10,14 +10,6 @@ import XCTest
|
|||
|
||||
final class DateTimeTests: XCTestCase {
|
||||
|
||||
func testInterval2DispatchInterval() {
|
||||
let i0 = 1.23.seconds
|
||||
XCTAssertEqual(i0.asDispatchTimeInterval(), DispatchTimeInterval.nanoseconds(Int(i0.nanoseconds)))
|
||||
|
||||
let i1 = 4.56.minutes + 7.89.hours
|
||||
XCTAssertEqual(i1.asDispatchTimeInterval(), DispatchTimeInterval.nanoseconds(Int(i1.nanoseconds)))
|
||||
}
|
||||
|
||||
func testIntervalConvertible() {
|
||||
XCTAssertEqual(1.nanoseconds, Interval(nanoseconds: 1))
|
||||
XCTAssertEqual(2.microseconds, Interval(nanoseconds: 2 * K.ns_per_us))
|
||||
|
@ -53,7 +45,6 @@ final class DateTimeTests: XCTestCase {
|
|||
}
|
||||
|
||||
static var allTests = [
|
||||
("testInterval2DispatchInterval", testInterval2DispatchInterval),
|
||||
("testInterval", testIntervalConvertible),
|
||||
("testTimeConstructor", testTimeConstructor),
|
||||
("testPeriodAnd", testPeriodAnd),
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// ExtensionsTests.swift
|
||||
// Schedule
|
||||
//
|
||||
// Created by Quentin Jin on 2018/7/24.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Schedule
|
||||
|
||||
final class ExtensionsTests: XCTestCase {
|
||||
|
||||
func testClampedAdding() {
|
||||
let a: Int = 1
|
||||
let b: Int = .max
|
||||
XCTAssertEqual(a.clampedAdding(b), Int.max)
|
||||
}
|
||||
|
||||
func testClampedSubtracting() {
|
||||
let a: Int = .min
|
||||
let b: Int = 1
|
||||
XCTAssertEqual(a.clampedSubtracting(b), Int.min)
|
||||
}
|
||||
|
||||
static var allTests = [
|
||||
("testClampedAdding", testClampedAdding),
|
||||
("testClampedSubtracting", testClampedSubtracting)
|
||||
]
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// TaskCenterTests.swift
|
||||
// Schedule
|
||||
//
|
||||
// Created by Quentin Jin on 2018/7/25.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Schedule
|
||||
|
||||
final class TaskCenterTests: XCTestCase {
|
||||
|
||||
func makeTask() -> Task {
|
||||
return Schedule.never.do { }
|
||||
}
|
||||
|
||||
var center: TaskCenter {
|
||||
return TaskCenter.shared
|
||||
}
|
||||
|
||||
func testAdd() {
|
||||
let task = makeTask()
|
||||
center.add(task)
|
||||
XCTAssertTrue(center.contains(task))
|
||||
}
|
||||
|
||||
func testRemove() {
|
||||
let task = makeTask()
|
||||
center.add(task)
|
||||
center.remove(task)
|
||||
XCTAssertFalse(center.contains(task))
|
||||
}
|
||||
|
||||
func testTag() {
|
||||
let task = makeTask()
|
||||
let tag0 = UUID().uuidString
|
||||
center.add(task, withTag: tag0)
|
||||
XCTAssertTrue(center.tasks(forTag: tag0).contains(task))
|
||||
|
||||
let tag1 = UUID().uuidString
|
||||
center.add(tag: tag1, to: task)
|
||||
XCTAssertTrue(center.tasks(forTag: tag1).contains(task))
|
||||
|
||||
center.remove(tag: tag0, from: task)
|
||||
XCTAssertFalse(center.tasks(forTag: tag0).contains(task))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// WeakSetTests.swift
|
||||
// Schedule
|
||||
//
|
||||
// Created by Quentin Jin on 2018/7/25.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import Schedule
|
||||
|
||||
private class Object { }
|
||||
|
||||
final class WeakSetTests: XCTestCase {
|
||||
|
||||
func testAdd() {
|
||||
var set = WeakSet<Object>()
|
||||
|
||||
let block = {
|
||||
let obj = Object()
|
||||
set.add(obj)
|
||||
}
|
||||
|
||||
block()
|
||||
XCTAssertEqual(set.count, 0)
|
||||
|
||||
let obj = Object()
|
||||
set.add(obj)
|
||||
set.add(obj)
|
||||
XCTAssertEqual(set.count, 1)
|
||||
}
|
||||
|
||||
func testRemove() {
|
||||
var set = WeakSet<Object>()
|
||||
let obj = Object()
|
||||
set.add(obj)
|
||||
set.remove(obj)
|
||||
XCTAssertEqual(set.count, 0)
|
||||
}
|
||||
|
||||
static var allTests = [
|
||||
("testAdd", testAdd),
|
||||
("testRemove", testRemove)
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue