Merge pull request #35 from jianstm/2.x

2.x
This commit is contained in:
Quentin Jin 2019-03-14 22:10:43 +08:00 committed by GitHub
commit 0d16c26cdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1295 additions and 1907 deletions

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View File

@ -5,10 +5,12 @@ import PackageDescription
let package = Package(
name: "Schedule",
products: [
.library(name: "Schedule", targets: ["Schedule"])
.library(name: "Schedule", targets: ["Schedule"]),
.executable(name: "ScheduleDemo", targets: ["ScheduleDemo"])
],
targets: [
.target(name: "Schedule"),
.testTarget(name: "ScheduleTests", dependencies: ["Schedule"])
.testTarget(name: "ScheduleTests", dependencies: ["Schedule"]),
.target(name: "ScheduleDemo", dependencies: ["Schedule"])
]
)

View File

@ -1,27 +0,0 @@
//: Playground - noun: a place where people can play
import PlaygroundSupport
import Schedule
PlaygroundPage.current.needsIndefiniteExecution = true
Plan.after(1.second).do {
print("1 second passed!")
}
Plan.after(1.minute, repeating: 0.5.seconds).do {
print("Ping!")
}
Plan.every("one minute and ten seconds").do {
print("One minute and ten seconds have elapsed!")
}
Plan.every(.monday, .tuesday, .wednesday, .thursday, .friday).at(6, 30).do {
print("Get up!")
}
Plan.every(.june(14)).at("9:30").do {
print("Happy birthday!")
}

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='5.0' target-platform='macos' display-mode='raw' last-migration='1000'>
<timeline fileName='timeline.xctimeline'/>
</playground>

View File

@ -1,22 +1,18 @@
Pod::Spec.new do |s|
s.name = "Schedule"
s.version = "1.0.0"
s.summary = "A lightweight task scheduler for Swift."
s.description = <<-DESC
Schedule is a missing lightweight task scheduler for Swift.
It allows you run timed tasks using an incredibly human-friendly syntax.
DESC
s.version = "2.0.0-beta.1"
s.license = { :type => "MIT" }
s.homepage = "https://github.com/jianstm/Schedule"
s.license = { :type => "MIT", :file => "LICENSE" }
s.author = { "Quentin Jin" => "jianstm@gmail.com" }
s.source = { :git => "https://github.com/jianstm/Schedule.git",
:tag => "#{s.version}" }
s.summary = "Lightweight timing task scheduler"
s.source = { :git => "https://github.com/jianstm/Schedule.git", :tag => "#{s.version}" }
s.source_files = "Sources/Schedule/*.swift"
s.requires_arc = true
s.swift_version = "4.2"
s.ios.deployment_target = "8.0"
s.osx.deployment_target = "10.10"
s.tvos.deployment_target = "9.0"
s.watchos.deployment_target = "2.0"
s.ios.deployment_target = "10.0"
s.osx.deployment_target = "10.12"
s.tvos.deployment_target = "10.0"
s.watchos.deployment_target = "3.0"
end

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@ -15,13 +15,11 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<string>2.0.0-beta.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2018 Quentin Jin. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>

File diff suppressed because it is too large Load Diff

View File

@ -4,4 +4,4 @@
<FileRef
location = "self:">
</FileRef>
</Workspace>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
<false/>
</dict>
</plist>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1000"
LastUpgradeVersion = "9999"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@ -14,9 +14,23 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "52D6D97B1BEFF229002C0205"
BlueprintIdentifier = "Schedule::Schedule"
BuildableName = "Schedule.framework"
BlueprintName = "Schedule-iOS"
BlueprintName = "Schedule"
ReferencedContainer = "container:Schedule.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Schedule::ScheduleDemo"
BuildableName = "ScheduleDemo"
BlueprintName = "ScheduleDemo"
ReferencedContainer = "container:Schedule.xcodeproj">
</BuildableReference>
</BuildActionEntry>
@ -26,30 +40,19 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
codeCoverageEnabled = "YES"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "52D6D9851BEFF229002C0205"
BuildableName = "Schedule-iOS Tests.xctest"
BlueprintName = "Schedule-iOS Tests"
BlueprintIdentifier = "Schedule::ScheduleTests"
BuildableName = "ScheduleTests.xctest"
BlueprintName = "ScheduleTests"
ReferencedContainer = "container:Schedule.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "52D6D97B1BEFF229002C0205"
BuildableName = "Schedule.framework"
BlueprintName = "Schedule-iOS"
ReferencedContainer = "container:Schedule.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
@ -66,9 +69,9 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "52D6D97B1BEFF229002C0205"
BlueprintIdentifier = "Schedule::Schedule"
BuildableName = "Schedule.framework"
BlueprintName = "Schedule-iOS"
BlueprintName = "Schedule"
ReferencedContainer = "container:Schedule.xcodeproj">
</BuildableReference>
</MacroExpansion>
@ -81,15 +84,6 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "52D6D97B1BEFF229002C0205"
BuildableName = "Schedule.framework"
BlueprintName = "Schedule-iOS"
ReferencedContainer = "container:Schedule.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

View File

@ -1,101 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1000"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "52D6DA0E1BF000BD002C0205"
BuildableName = "Schedule.framework"
BlueprintName = "Schedule-macOS"
ReferencedContainer = "container:Schedule.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
codeCoverageEnabled = "YES"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DD7502791C68FCFC006590AF"
BuildableName = "Schedule-macOS Tests.xctest"
BlueprintName = "Schedule-macOS Tests"
ReferencedContainer = "container:Schedule.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "52D6DA0E1BF000BD002C0205"
BuildableName = "Schedule.framework"
BlueprintName = "Schedule-macOS"
ReferencedContainer = "container:Schedule.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "52D6DA0E1BF000BD002C0205"
BuildableName = "Schedule.framework"
BlueprintName = "Schedule-macOS"
ReferencedContainer = "container:Schedule.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "52D6DA0E1BF000BD002C0205"
BuildableName = "Schedule.framework"
BlueprintName = "Schedule-macOS"
ReferencedContainer = "container:Schedule.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,101 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1000"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "52D6D9EF1BEFFFBE002C0205"
BuildableName = "Schedule.framework"
BlueprintName = "Schedule-tvOS"
ReferencedContainer = "container:Schedule.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
codeCoverageEnabled = "YES"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DD75028C1C690C7A006590AF"
BuildableName = "Schedule-tvOS Tests.xctest"
BlueprintName = "Schedule-tvOS Tests"
ReferencedContainer = "container:Schedule.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "52D6D9EF1BEFFFBE002C0205"
BuildableName = "Schedule.framework"
BlueprintName = "Schedule-tvOS"
ReferencedContainer = "container:Schedule.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "52D6D9EF1BEFFFBE002C0205"
BuildableName = "Schedule.framework"
BlueprintName = "Schedule-tvOS"
ReferencedContainer = "container:Schedule.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "52D6D9EF1BEFFFBE002C0205"
BuildableName = "Schedule.framework"
BlueprintName = "Schedule-tvOS"
ReferencedContainer = "container:Schedule.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1000"
LastUpgradeVersion = "9999"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@ -14,9 +14,9 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "52D6D9E11BEFFF6E002C0205"
BuildableName = "Schedule.framework"
BlueprintName = "Schedule-watchOS"
BlueprintIdentifier = "Schedule::ScheduleDemo"
BuildableName = "ScheduleDemo"
BlueprintName = "ScheduleDemo"
ReferencedContainer = "container:Schedule.xcodeproj">
</BuildableReference>
</BuildActionEntry>
@ -26,9 +26,18 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
codeCoverageEnabled = "YES"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Schedule::ScheduleTests"
BuildableName = "ScheduleTests.xctest"
BlueprintName = "ScheduleTests"
ReferencedContainer = "container:Schedule.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
@ -43,15 +52,16 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "52D6D9E11BEFFF6E002C0205"
BuildableName = "Schedule.framework"
BlueprintName = "Schedule-watchOS"
BlueprintIdentifier = "Schedule::ScheduleDemo"
BuildableName = "ScheduleDemo"
BlueprintName = "ScheduleDemo"
ReferencedContainer = "container:Schedule.xcodeproj">
</BuildableReference>
</MacroExpansion>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
@ -61,15 +71,6 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "52D6D9E11BEFFF6E002C0205"
BuildableName = "Schedule.framework"
BlueprintName = "Schedule-watchOS"
ReferencedContainer = "container:Schedule.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

View File

@ -1,21 +1,30 @@
import Foundation
/// A value box that can read and write the underlying value atomically.
final class Atomic<T> {
private var _value: T
private let _lock = NSLock()
private var v: T
private let lock = NSLock()
init(_ value: T) {
self._value = value
self.v = value
}
/// Creates a snapshot of the value nonatomically.
@inline(__always)
func snapshot() -> T {
return v
}
/// Reads the value atomically.
@inline(__always)
func read<U>(_ body: (T) -> U) -> U {
return _lock.withLock { body(_value) }
return lock.withLock { body(v) }
}
/// Writes the value atomically.
@inline(__always)
func write<U>(_ body: (inout T) -> U) -> U {
return _lock.withLock { body(&_value) }
return lock.withLock { body(&v) }
}
}

View File

@ -1,77 +0,0 @@
import Foundation
struct BucketKey {
private let _underlying: UInt64
init(underlying: UInt64) {
self._underlying = underlying
}
func increased() -> BucketKey {
let i = _underlying &+ 1
return BucketKey(underlying: i)
}
}
extension BucketKey: Equatable {
static func == (lhs: BucketKey, rhs: BucketKey) -> Bool {
return lhs._underlying == rhs._underlying
}
}
struct Bucket<Element> {
private typealias Entry = (key: BucketKey, element: Element)
private var key = BucketKey(underlying: 0)
private var entries: [Entry] = []
@discardableResult
mutating func add(_ new: Element) -> BucketKey {
defer { key = key.increased() }
let entry = (key: key, element: new)
entries.append(entry)
return key
}
@discardableResult
mutating func removeElement(for key: BucketKey) -> Element? {
if let index = entries.firstIndex(where: { $0.key == key }) {
return entries.remove(at: index).element
}
return nil
}
func element(for key: BucketKey) -> Element? {
if let entry = entries.first(where: { $0.key == key }) {
return entry.element
}
return nil
}
mutating func removeAll() {
entries.removeAll()
}
var count: Int {
return entries.count
}
}
extension Bucket: Sequence {
func makeIterator() -> AnyIterator<Element> {
var iterator = entries.makeIterator()
return AnyIterator<Element> {
return iterator.next()?.element
}
}
}

View File

@ -0,0 +1,70 @@
import Foundation
struct CabinetKey: Equatable {
private let i: UInt64
init(underlying: UInt64) {
self.i = underlying
}
func increased() -> CabinetKey {
return CabinetKey(underlying: i &+ 1)
}
static func == (lhs: CabinetKey, rhs: CabinetKey) -> Bool {
return lhs.i == rhs.i
}
}
struct Cabinet<Element> {
private typealias Entry = (key: CabinetKey, element: Element)
private var key = CabinetKey(underlying: 0)
private var entries: [Entry] = []
@discardableResult
mutating func append(_ new: Element) -> CabinetKey {
defer { key = key.increased() }
let entry = (key: key, element: new)
entries.append(entry)
return key
}
func get(_ key: CabinetKey) -> Element? {
if let entry = entries.first(where: { $0.key == key }) {
return entry.element
}
return nil
}
@discardableResult
mutating func delete(_ key: CabinetKey) -> Element? {
if let i = entries.firstIndex(where: { $0.key == key }) {
return entries.remove(at: i).element
}
return nil
}
mutating func clear() {
entries.removeAll()
}
var count: Int {
return entries.count
}
}
extension Cabinet: Sequence {
func makeIterator() -> AnyIterator<Element> {
var iterator = entries.makeIterator()
return AnyIterator<Element> {
return iterator.next()?.element
}
}
}

View File

@ -6,34 +6,36 @@ private var deinitObserverKey: Void = ()
class DeinitObserver {
private(set) weak var observed: AnyObject?
private(set) weak var object: AnyObject?
private var block: (() -> Void)?
private var action: (() -> Void)?
private init(_ block: @escaping () -> Void) {
self.block = block
private init(_ action: @escaping () -> Void) {
self.action = action
}
@discardableResult
static func observe(_ observed: AnyObject, onDeinit block: @escaping () -> Void) -> DeinitObserver {
let observer = DeinitObserver(block)
observer.observed = observed
static func observe(
_ object: AnyObject,
onDeinit action: @escaping () -> Void
) -> DeinitObserver {
let observer = DeinitObserver(action)
observer.object = object
objc_setAssociatedObject(observed, &deinitObserverKey, observer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_setAssociatedObject(object, &deinitObserverKey, observer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return observer
}
func cancel() {
block = nil
if let o = observed {
func invalidate() {
action = nil
if let o = object {
objc_setAssociatedObject(o, &deinitObserverKey, nil, .OBJC_ASSOCIATION_ASSIGN)
}
}
deinit {
block?()
action?()
}
}

View File

@ -0,0 +1,59 @@
//
// Deprecated.swift
// Schedule
//
// Created by Quentin Jin on 2019/3/4.
// Copyright © 2019 Schedule. All rights reserved.
//
import Foundation
extension Monthday {
/// A Boolean value indicating whether today is the monthday.
@available(*, deprecated, message: "Use date.is(_:Monthday)")
public var isToday: Bool {
let components = toDateComponents()
let m = Calendar.gregorian.component(.month, from: Date())
let d = Calendar.gregorian.component(.day, from: Date())
return m == components.month && d == components.day
}
}
extension Weekday {
/// A Boolean value indicating whether today is the weekday.
@available(*, deprecated, message: "Use date.is(_:Weekday)")
public var isToday: Bool {
return Calendar.gregorian.component(.weekday, from: Date()) == rawValue
}
}
extension Time {
/// The interval between this time and zero o'clock.
@available(*, deprecated, renamed: "intervalSinceStartOfDay")
public var intervalSinceZeroClock: Interval {
return hour.hours + minute.minutes + second.seconds + nanosecond.nanoseconds
}
}
extension Plan {
/// Creates a plan from an interval sequence.
/// The task will be executed after each interval in the sequence.
@available(*, deprecated, message: "Use Plan.of")
public static func from<S>(_ sequence: S) -> Plan where S: Sequence, S.Element == Interval {
return Plan.of(sequence)
}
/// Creates a plan from a date sequence.
/// The task will be executed at each date in the sequence.
/// - Note: Returns `Plan.never` if given no parameters.
@available(*, deprecated, message: "Use Plan.of")
public static func from<S>(_ sequence: S) -> Plan where S: Sequence, S.Element == Date {
return Plan.make(sequence.makeIterator)
}
}

View File

@ -22,21 +22,21 @@ extension Int {
extension Calendar {
static var standard: Calendar {
var calendar = Calendar(identifier: .gregorian)
calendar.locale = Locale(identifier: "en_US_POSIX")
return calendar
}
static let gregorian: Calendar = {
var cal = Calendar(identifier: .gregorian)
cal.locale = Locale(identifier: "en_US_POSIX")
return cal
}()
}
extension Date {
var start: Date {
return Calendar.standard.startOfDay(for: self)
var startOfToday: Date {
return Calendar.gregorian.startOfDay(for: self)
}
}
extension NSLock {
extension NSLocking {
@inline(__always)
func withLock<T>(_ body: () throws -> T) rethrows -> T {

View File

@ -62,6 +62,8 @@ extension Interval: Hashable {
extension Interval: CustomStringConvertible {
/// A textual representation of this interval.
///
/// Interval: 1000 nanoseconds
public var description: String {
return "Interval: \(nanoseconds.clampedToInt()) nanoseconds"
}
@ -70,13 +72,15 @@ extension Interval: CustomStringConvertible {
extension Interval: CustomDebugStringConvertible {
/// A textual representation of this interval for debugging.
///
/// Interval: 1000 nanoseconds
public var debugDescription: String {
return description
}
}
// MARK: - Comparing
extension Interval {
extension Interval: Comparable {
/// Compares two intervals.
///
@ -86,6 +90,14 @@ extension Interval {
return now.adding(self).compare(now.adding(other))
}
/// Returns a boolean value indicating whether the first interval is
/// less than the second interval.
///
/// A negative interval is always less than a positive interval.
public static func < (lhs: Interval, rhs: Interval) -> Bool {
return lhs.compare(rhs) == .orderedAscending
}
/// Returns a boolean value indicating whether this interval is longer
/// than the given value.
public func isLonger(than other: Interval) -> Bool {
@ -101,41 +113,30 @@ extension Interval {
/// Returns the longest interval of the given values.
/// - Note: Returns initialized with `init(nanoseconds: 0)` if given no parameters.
public static func longest(_ intervals: Interval...) -> Interval {
return Interval.longest(intervals)
return longest(intervals)!
}
/// Returns the longest interval of the given values.
/// - Note: Returns initialized with `init(nanoseconds: 0)` if given an empty array.
public static func longest(_ intervals: [Interval]) -> Interval {
guard !intervals.isEmpty else { return .init(nanoseconds: 0) }
public static func longest(_ intervals: [Interval]) -> Interval? {
guard !intervals.isEmpty else { return nil }
return intervals.sorted(by: { $0.magnitude > $1.magnitude })[0]
}
/// Returns the shortest interval of the given values.
/// - Note: Returns initialized with `init(nanoseconds: 0)` if given no parameters.
public static func shortest(_ intervals: Interval...) -> Interval {
return Interval.shortest(intervals)
return shortest(intervals)!
}
/// Returns the shortest interval of the given values.
/// - Note: Returns initialized with `init(nanoseconds: 0)` if given an empty array.
public static func shortest(_ intervals: [Interval]) -> Interval {
guard !intervals.isEmpty else { return .init(nanoseconds: 0) }
public static func shortest(_ intervals: [Interval]) -> Interval? {
guard !intervals.isEmpty else { return nil }
return intervals.sorted(by: { $0.magnitude < $1.magnitude })[0]
}
}
extension Interval: Comparable {
/// Returns a Boolean value indicating whether the first interval is
/// less than the second interval.
///
/// A negative interval is always less than a positive interval.
public static func < (lhs: Interval, rhs: Interval) -> Bool {
return lhs.compare(rhs) == .orderedAscending
}
}
// MARK: - Adding & Subtracting
extension Interval {
/// Returns a new interval by multipling this interval by the given number.

View File

@ -1,6 +1,6 @@
import Foundation
/// `Monthday` represents a day of a month without years.
/// `Monthday` represents a day of a month.
public enum Monthday {
case january(Int)
@ -27,13 +27,6 @@ public enum Monthday {
case december(Int)
/// A Boolean value indicating whether today is the weekday.
public var isToday: Bool {
let lhs = Calendar.standard.dateComponents(in: TimeZone.current, from: Date())
let rhs = toDateComponents()
return lhs.month == rhs.month && lhs.day == rhs.day
}
/// Returns a dateComponenets of the monthday, using gregorian calender and
/// current time zone.
public func toDateComponents() -> DateComponents {
@ -52,28 +45,53 @@ public enum Monthday {
case .november(let n): month = 11; day = n
case .december(let n): month = 12; day = n
}
return DateComponents(calendar: Calendar.standard,
timeZone: TimeZone.current,
month: month,
day: day)
return DateComponents(
calendar: Calendar.gregorian,
timeZone: TimeZone.current,
month: month,
day: day
)
}
}
extension Date {
/// Returns a boolean value indicating whether today is the monthday.
public func `is`(_ monthday: Monthday) -> Bool {
let components = monthday.toDateComponents()
let m = Calendar.gregorian.component(.month, from: self)
let d = Calendar.gregorian.component(.day, from: self)
return m == components.month && d == components.day
}
}
extension Monthday: CustomStringConvertible {
/// A textual representation of this monthday.
///
/// "Monthday: May 1st"
public var description: String {
let month = toDateComponents().month!
let day = toDateComponents().day!
let components = toDateComponents()
let monthDesc = Calendar.standard.monthSymbols[month - 1]
return "Monthday: \(monthDesc) \(day)"
let m = components.month!
let d = components.day!
let ms = Calendar.gregorian.monthSymbols[m - 1]
let fmt = NumberFormatter()
fmt.numberStyle = .ordinal
let ds = fmt.string(from: NSNumber(value: d))!
return "Monthday: \(ms) \(ds)"
}
}
extension Monthday: CustomDebugStringConvertible {
/// A textual representation of this monthday for debugging.
///
/// "Monthday: May 1st"
public var debugDescription: String {
return description
}

View File

@ -102,9 +102,11 @@ public struct Period {
/// Returns a new period by adding an interval to this period.
public func adding(_ interval: Interval) -> Period {
return Period(years: years, months: months, days: days,
hours: hours, minutes: minutes, seconds: seconds,
nanoseconds: nanoseconds.clampedAdding(interval.nanoseconds.clampedToInt())).tidied(to: .day)
return Period(
years: years, months: months, days: days,
hours: hours, minutes: minutes, seconds: seconds,
nanoseconds: nanoseconds.clampedAdding(interval.nanoseconds.clampedToInt())
).tidied(to: .day)
}
/// Adds two periods and produces their sum.
@ -166,7 +168,7 @@ extension Date {
/// Returns a new date by adding a period to this date.
public func adding(_ period: Period) -> Date {
return Calendar.standard.date(byAdding: period.toDateComponents(), to: self) ?? .distantFuture
return Calendar.gregorian.date(byAdding: period.toDateComponents(), to: self) ?? .distantFuture
}
/// Returns a date with a period added to it.
@ -197,6 +199,8 @@ extension Int {
extension Period: CustomStringConvertible {
/// A textual representation of this period.
///
/// Period: 1 year(s) 2 month(s) 3 day(s)
public var description: String {
let period = tidied(to: .day)
var desc = "Period:"
@ -214,6 +218,8 @@ extension Period: CustomStringConvertible {
extension Period: CustomDebugStringConvertible {
/// A textual representation of this period for debugging.
///
/// Period: 1 year(s) 2 month(s) 3 day(s)
public var debugDescription: String {
return description
}

View File

@ -6,13 +6,14 @@ import Foundation
/// `Plan` is `Interval` based.
public struct Plan {
private var sequence: AnySequence<Interval>
private var iSeq: AnySequence<Interval>
private init<S>(_ sequence: S) where S: Sequence, S.Element == Interval {
self.sequence = AnySequence(sequence)
iSeq = AnySequence(sequence)
}
func makeIterator() -> AnyIterator<Interval> {
return sequence.makeIterator()
return iSeq.makeIterator()
}
/// Schedules a task with this plan.
@ -67,24 +68,17 @@ extension Plan {
return Plan(AnySequence(makeUnderlyingIterator))
}
/// Creates a plan from an interval sequence.
/// The task will be executed after each interval in the sequence.
public static func from<S>(_ sequence: S) -> Plan where S: Sequence, S.Element == Interval {
return Plan(sequence)
}
/// 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(_ intervals: [Interval]) -> Plan {
guard !intervals.isEmpty else { return .never }
public static func of<S>(_ intervals: S) -> Plan where S: Sequence, S.Element == Interval {
return Plan(intervals)
}
}
@ -128,25 +122,17 @@ extension Plan {
}
}
/// Creates a plan from a date sequence.
/// The task will be executed at each date in the sequence.
public static func from<S>(_ sequence: S) -> Plan where S: Sequence, S.Element == Date {
return Plan.make(sequence.makeIterator)
/// 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(_ 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(_ dates: [Date]) -> Plan {
guard !dates.isEmpty else { return .never }
return Plan.from(dates)
public static func of<S>(_ sequence: S) -> Plan where S: Sequence, S.Element == Date {
return Plan.make(sequence.makeIterator)
}
/// A dates sequence corresponding to this plan.
@ -308,7 +294,7 @@ extension Plan {
/// Creates a plan that executes the task every period.
public static func every(_ period: Period) -> Plan {
return Plan.make { () -> AnyIterator<Interval> in
let calendar = Calendar.standard
let calendar = Calendar.gregorian
var last: Date!
return AnyIterator {
last = last ?? Date()
@ -346,8 +332,8 @@ extension Plan {
/// Returns a plan at the specific time.
public func at(_ time: Time) -> Plan {
guard !self.plan.isNever() else { return .never }
var interval = time.intervalSinceZeroClock
var interval = time.intervalSinceStartOfDay
return Plan.make { () -> AnyIterator<Interval> in
let it = self.plan.makeIterator()
return AnyIterator {
@ -370,7 +356,7 @@ extension Plan {
else {
return .never
}
return at(time)
}
@ -396,7 +382,7 @@ extension Plan {
/// - Note: Returns `Plan.never` if given an empty array.
public func at(_ time: [Int]) -> Plan {
guard !time.isEmpty, !self.plan.isNever() else { return .never }
let hour = time[0]
let minute = time.count > 1 ? time[1] : 0
let second = time.count > 2 ? time[2] : 0
@ -412,16 +398,16 @@ extension Plan {
/// Creates a plan that executes the task every specific weekday.
public static func every(_ weekday: Weekday) -> DateMiddleware {
let plan = Plan.make { () -> AnyIterator<Date> in
let calendar = Calendar.standard
var date: Date!
let calendar = Calendar.gregorian
var date: Date?
return AnyIterator<Date> {
if weekday.isToday {
date = Date().start
} else if date == nil {
let components = weekday.toDateComponents()
date = calendar.nextDate(after: date, matching: components, matchingPolicy: .strict)
if let d = date {
date = calendar.date(byAdding: .day, value: 7, to: d)
} else if Date().is(weekday) {
date = Date().startOfToday
} else {
date = calendar.date(byAdding: .day, value: 7, to: date)
let components = weekday.toDateComponents()
date = calendar.nextDate(after: Date(), matching: components, matchingPolicy: .strict)
}
return date
}
@ -434,12 +420,12 @@ extension Plan {
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.
public static func every(_ weekdays: [Weekday]) -> DateMiddleware {
guard !weekdays.isEmpty else { return .init(plan: .never) }
var plan = every(weekdays[0]).plan
if weekdays.count > 1 {
for i in 1..<weekdays.count {
@ -452,16 +438,16 @@ extension Plan {
/// Creates a plan that executes the task every specific day in the month.
public static func every(_ monthday: Monthday) -> DateMiddleware {
let plan = Plan.make { () -> AnyIterator<Date> in
let calendar = Calendar.standard
var date: Date!
let calendar = Calendar.gregorian
var date: Date?
return AnyIterator<Date> {
if monthday.isToday {
date = Date().start
} else if date == nil {
if let d = date {
date = calendar.date(byAdding: .year, value: 1, to: d)
} else if Date().is(monthday) {
date = Date().startOfToday
} else {
let components = monthday.toDateComponents()
date = calendar.nextDate(after: date, matching: components, matchingPolicy: .strict)
} else {
date = calendar.date(byAdding: .year, value: 1, to: date)
date = calendar.nextDate(after: Date(), matching: components, matchingPolicy: .strict)
}
return date
}
@ -474,12 +460,12 @@ extension Plan {
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.
public static func every(_ mondays: [Monthday]) -> DateMiddleware {
guard !mondays.isEmpty else { return .init(plan: .never) }
var plan = every(mondays[0]).plan
if mondays.count > 1 {
for i in 1..<mondays.count {
@ -492,7 +478,7 @@ extension Plan {
extension Plan {
public func isNever() -> Bool {
return self.sequence.makeIterator().next() == nil
return self.iSeq.makeIterator().next() == nil
}
}
@ -516,7 +502,7 @@ extension Plan {
}
}
}
/// Creates a new plan that is offset by the specified interval.
///
/// If the specified interval offset is `nil`, then no offset is

View File

@ -3,13 +3,13 @@ import Foundation
/// `ActionKey` represents a token that can be used to remove the action.
public struct ActionKey {
fileprivate let bucketKey: BucketKey
fileprivate let cabinetKey: CabinetKey
}
extension BucketKey {
extension CabinetKey {
func asActionKey() -> ActionKey {
return ActionKey(bucketKey: self)
return ActionKey(cabinetKey: self)
}
}
@ -18,19 +18,17 @@ open class Task {
public typealias Action = (Task) -> Void
private let _lock = NSLock()
private weak var _taskCenter: TaskCenter?
private let _mutex = NSRecursiveLock()
private var _iterator: AnyIterator<Interval>
private var _timer: DispatchSourceTimer
private lazy var _onElapseActions = Bucket<Action>()
private var _onDeinitActions = Bucket<Action>()
private lazy var _onElapseActions = Cabinet<Action>()
private lazy var _onDeinitActions = Cabinet<Action>()
private lazy var _suspensions: UInt64 = 0
private lazy var _timeline = Timeline()
private lazy var _tags: Set<String> = []
private lazy var _countOfExecutions: Int = 0
private lazy var _lifetime: Interval = Int.max.seconds
@ -44,6 +42,9 @@ open class Task {
return timer
}()
open internal(set) weak var taskCenter: TaskCenter?
let taskCenterMutex = NSRecursiveLock()
init(plan: Plan,
queue: DispatchQueue?,
onElapse: @escaping (Task) -> Void) {
@ -51,7 +52,7 @@ open class Task {
_iterator = plan.makeIterator()
_timer = DispatchSource.makeTimerSource(queue: queue)
_onElapseActions.add(onElapse)
_onElapseActions.append(onElapse)
_timer.setEventHandler { [weak self] in
guard let self = self else { return }
@ -63,8 +64,8 @@ open class Task {
_timeline.estimatedNextExecution = Date().adding(interval)
}
TaskCenter.default.add(self)
_timer.resume()
TaskCenter.default.add(self)
}
deinit {
@ -81,7 +82,7 @@ open class Task {
}
private func scheduleNext() {
_lock.withLock {
_mutex.withLock {
let now = Date()
var estimated = _timeline.estimatedNextExecution ?? now
repeat {
@ -99,7 +100,7 @@ open class Task {
/// Execute this task now, without disrupting its plan.
public func execute() {
let actions = _lock.withLock { () -> Bucket<Task.Action> in
let actions = _mutex.withLock { () -> Cabinet<Task.Action> in
let now = Date()
if _timeline.firstExecution == nil {
_timeline.firstExecution = now
@ -124,29 +125,16 @@ open class Task {
}
#endif
open internal(set) var taskCenter: TaskCenter? {
get {
return _lock.withLock {
_taskCenter
}
}
set {
_lock.withLock {
_taskCenter = newValue
}
}
}
/// The number of times the task has been executed.
public var countOfExecutions: Int {
return _lock.withLock {
return _mutex.withLock {
_countOfExecutions
}
}
/// A Boolean indicating whether the task was canceled.
public var isCancelled: Bool {
return _lock.withLock {
return _mutex.withLock {
_timer.isCancelled
}
}
@ -155,7 +143,7 @@ open class Task {
/// Reschedules this task with the new plan.
public func reschedule(_ new: Plan) {
_lock.withLock {
_mutex.withLock {
_iterator = new.makeIterator()
}
scheduleNext()
@ -163,14 +151,14 @@ open class Task {
/// Suspensions of this task.
public var suspensions: UInt64 {
return _lock.withLock {
return _mutex.withLock {
_suspensions
}
}
/// Suspends this task.
public func suspend() {
_lock.withLock {
_mutex.withLock {
if _suspensions < UInt64.max {
_timer.suspend()
_suspensions += 1
@ -180,7 +168,7 @@ open class Task {
/// Resumes this task.
public func resume() {
_lock.withLock {
_mutex.withLock {
if _suspensions > 0 {
_timer.resume()
_suspensions -= 1
@ -190,7 +178,7 @@ open class Task {
/// Cancels this task.
public func cancel() {
_lock.withLock {
_mutex.withLock {
_timer.cancel()
}
TaskCenter.default.remove(self)
@ -198,8 +186,8 @@ open class Task {
@discardableResult
open func onDeinit(_ body: @escaping Action) -> ActionKey {
return _lock.withLock {
return _onDeinitActions.add(body).asActionKey()
return _mutex.withLock {
return _onDeinitActions.append(body).asActionKey()
}
}
@ -207,21 +195,21 @@ open class Task {
/// The snapshot timeline of this task.
public var timeline: Timeline {
return _lock.withLock {
return _mutex.withLock {
_timeline
}
}
/// The lifetime of this task.
public var lifetime: Interval {
return _lock.withLock {
return _mutex.withLock {
_lifetime
}
}
/// The rest of lifetime.
public var restOfLifetime: Interval {
return _lock.withLock {
return _mutex.withLock {
_lifetime - Date().interval(since: _timeline.initialization)
}
}
@ -236,16 +224,16 @@ open class Task {
public func setLifetime(_ interval: Interval) -> Bool {
guard restOfLifetime.isPositive else { return false }
_lock.lock()
_mutex.lock()
let age = Date().interval(since: _timeline.initialization)
guard age.isShorter(than: interval) else {
_lock.unlock()
_mutex.unlock()
return false
}
_lifetime = interval
_lifetimeTimer.schedule(after: interval - age)
_lock.unlock()
_mutex.unlock()
return true
}
@ -261,7 +249,7 @@ open class Task {
guard rest.isPositive else { return false }
rest += interval
guard rest.isPositive else { return false }
_lock.withLock {
_mutex.withLock {
_lifetime += interval
_lifetimeTimer.schedule(after: rest)
}
@ -283,7 +271,7 @@ open class Task {
/// The number of actions in this task.
public var countOfActions: Int {
return _lock.withLock {
return _mutex.withLock {
_onElapseActions.count
}
}
@ -291,94 +279,28 @@ open class Task {
/// Adds action to this task.
@discardableResult
public func addAction(_ action: @escaping (Task) -> Void) -> ActionKey {
return _lock.withLock {
return _onElapseActions.add(action).asActionKey()
return _mutex.withLock {
return _onElapseActions.append(action).asActionKey()
}
}
/// Removes action by key from this task.
public func removeAction(byKey key: ActionKey) {
_lock.withLock {
_ = _onElapseActions.removeElement(for: key.bucketKey)
_mutex.withLock {
_ = _onElapseActions.delete(key.cabinetKey)
}
}
/// Removes all actions from this task.
public func removeAllActions() {
_lock.withLock {
_onElapseActions.removeAll()
_mutex.withLock {
_onElapseActions.clear()
}
}
// MARK: - Tag
/// All tags associated with this task.
public var tags: Set<String> {
return _lock.withLock {
_tags
}
}
/// Adds tags to this task.
public func addTags(_ tags: [String]) {
var set = Set(tags)
_lock.lock()
set.subtract(_tags)
guard set.count > 0 else {
_lock.unlock()
return
}
_tags.formUnion(set)
_lock.unlock()
}
/// Adds tags to this task.
public func addTags(_ tags: String...) {
addTags(tags)
}
/// Adds the tag to this task.
public func addTag(_ tag: String) {
addTags(tag)
}
/// Removes tags from this task.
public func removeTags(_ tags: [String]) {
var set = Set(tags)
_lock.lock()
set.formIntersection(_tags)
guard set.count > 0 else {
_lock.unlock()
return
}
_tags.subtract(set)
_lock.unlock()
}
/// Removes tags from this task.
public func removeTags(_ tags: String...) {
removeTags(tags)
}
/// Removes the tag from this task.
public func removeTag(_ tag: String) {
removeTags(tag)
}
/// Suspends all tasks that have the tag.
public static func suspend(byTag tag: String) {
TaskCenter.default.tasksWithTag(tag).forEach { $0.suspend() }
}
/// Resumes all tasks that have the tag.
public static func resume(byTag tag: String) {
TaskCenter.default.tasksWithTag(tag).forEach { $0.resume() }
}
/// Cancels all tasks that have the tag.
public static func cancel(byTag tag: String) {
TaskCenter.default.tasksWithTag(tag).forEach { $0.cancel() }
open func add(to: TaskCenter) {
_mutex.lock()
}
}
@ -400,8 +322,7 @@ extension Task: CustomStringConvertible {
/// A textual representation of this task.
public var description: String {
return "Task: { " +
"\"isCancelled\": \(_timer.isCancelled)" +
"\"tags\": \(_tags), " +
"\"isCancelled\": \(_timer.isCancelled), " +
"\"countOfActions\": \(_onElapseActions.count), " +
"\"countOfExecutions\": \(_countOfExecutions), " +
"\"timeline\": \(_timeline)" +

View File

@ -2,88 +2,163 @@ import Foundation
private let _default = TaskCenter()
private class _TaskWrapper {
weak var task: Task?
extension TaskCenter {
let hashValue: Int
private class TaskBox: Hashable {
init(_ task: Task) {
self.task = task
self.hashValue = task.hashValue
}
}
weak var task: Task?
extension _TaskWrapper: Hashable {
// To find slot
let hashValue: Int
static func == (lhs: _TaskWrapper, rhs: _TaskWrapper) -> Bool {
return lhs.task == rhs.task
init(_ task: Task) {
self.task = task
self.hashValue = task.hashValue
}
// To find task
static func == (lhs: TaskBox, rhs: TaskBox) -> Bool {
return lhs.task == rhs.task
}
}
}
open class TaskCenter {
private let _lock = NSLock()
private var _tasks: Set<_TaskWrapper> = []
private let mutex = NSRecursiveLock()
private var taskMap: [String: Set<TaskBox>] = [:]
private var tagMap: [TaskBox: Set<String>] = [:]
open class var `default`: TaskCenter {
return _default
}
open func add(_ task: Task) {
if let center = task.taskCenter {
if center === self {
return
}
center.remove(task)
task.taskCenterMutex.lock()
defer {
task.taskCenterMutex.unlock()
}
if let center = task.taskCenter {
if center === self { return }
center.remove(task)
}
task.taskCenter = self
_lock.withLock {
let wrapper = _TaskWrapper(task)
_tasks.insert(wrapper)
mutex.withLock {
let box = TaskBox(task)
tagMap[box] = []
}
task.onDeinit { [weak self, weak wrapper = wrapper] task in
guard let self = self, let wrapper = wrapper else { return }
self._tasks.remove(wrapper)
}
task.onDeinit { [weak self] (t) in
guard let self = self else { return }
self.remove(t)
}
}
open func remove(_ task: Task) {
task.taskCenterMutex.lock()
defer {
task.taskCenterMutex.unlock()
}
guard task.taskCenter === self else {
return
}
task.taskCenter = nil
_lock.withLock {
let wrapper = _TaskWrapper(task)
_tasks.remove(wrapper)
}
}
open func tasksWithTag(_ tag: String) -> [Task] {
return _lock.withLock {
return _tasks.compactMap { wrapper in
if let task = wrapper.task, task.tags.contains(tag) {
return task
mutex.withLock {
let box = TaskBox(task)
if let tags = self.tagMap[box] {
for tag in tags {
self.taskMap[tag]?.remove(box)
}
return nil
self.tagMap[box] = nil
}
}
}
open func addTag(_ tag: String, to task: Task) {
addTags([tag], to: task)
}
open func addTags(_ tags: [String], to task: Task) {
guard task.taskCenter === self else { return }
mutex.withLock {
let box = TaskBox(task)
if tagMap[box] == nil {
tagMap[box] = []
}
for tag in tags {
tagMap[box]?.insert(tag)
if taskMap[tag] == nil {
taskMap[tag] = []
}
taskMap[tag]?.insert(box)
}
}
}
open func removeTag(_ tag: String, from task: Task) {
removeTags([tag], from: task)
}
open func removeTags(_ tags: [String], from task: Task) {
guard task.taskCenter === self else { return }
mutex.withLock {
let box = TaskBox(task)
for tag in tags {
tagMap[box]?.remove(tag)
taskMap[tag]?.remove(box)
}
}
}
open func tagsForTask(_ task: Task) -> [String] {
guard task.taskCenter === self else { return [] }
return mutex.withLock {
Array(tagMap[TaskBox(task)] ?? [])
}
}
open func tasksForTag(_ tag: String) -> [Task] {
return mutex.withLock {
taskMap[tag]?.compactMap { $0.task } ?? []
}
}
open var allTasks: [Task] {
return _lock.withLock {
return _tasks.compactMap { $0.task }
return mutex.withLock {
tagMap.compactMap { $0.key.task }
}
}
open var allTags: [String] {
return mutex.withLock {
taskMap.map { $0.key }
}
}
open func clear() {
_lock.withLock {
_tasks = []
mutex.withLock {
tagMap = [:]
taskMap = [:]
}
}
open func suspendByTag(_ tag: String) {
tasksForTag(tag).forEach { $0.suspend() }
}
open func resumeByTag(_ tag: String) {
tasksForTag(tag).forEach { $0.resume() }
}
open func cancelByTag(_ tag: String) {
tasksForTag(tag).forEach { $0.cancel() }
}
}

View File

@ -23,10 +23,12 @@ public struct Time {
/// Time(hour: 25) => nil
/// Time(hour: 1, minute: 61) => nil
public init?(hour: Int, minute: Int = 0, second: Int = 0, nanosecond: Int = 0) {
guard (0..<24).contains(hour),
(0..<60).contains(minute),
(0..<60).contains(second),
(0..<Int(1.second.nanoseconds)).contains(nanosecond) else { return nil }
guard
(0..<24).contains(hour),
(0..<60).contains(minute),
(0..<60).contains(second),
(0..<Int(1.second.nanoseconds)).contains(nanosecond)
else { return nil }
self.hour = hour
self.minute = minute
@ -85,15 +87,15 @@ public struct Time {
}
}
/// The interval between this time and zero o'clock.
public var intervalSinceZeroClock: Interval {
/// The interval between this time and start of today
public var intervalSinceStartOfDay: Interval {
return hour.hours + minute.minutes + second.seconds + nanosecond.nanoseconds
}
/// Returns a dateComponenets of the time, using gregorian calender and
/// current time zone.
public func toDateComponents() -> DateComponents {
return DateComponents(calendar: Calendar.standard,
return DateComponents(calendar: Calendar.gregorian,
timeZone: TimeZone.current,
hour: hour,
minute: minute,
@ -105,6 +107,8 @@ public struct Time {
extension Time: CustomStringConvertible {
/// A textual representation of this time.
///
/// "Time: 11:11:11.111"
public var description: String {
let h = "\(hour)".padding(toLength: 2, withPad: "0", startingAt: 0)
let m = "\(minute)".padding(toLength: 2, withPad: "0", startingAt: 0)
@ -117,6 +121,8 @@ extension Time: CustomStringConvertible {
extension Time: CustomDebugStringConvertible {
/// A textual representation of this time for debugging.
///
/// "Time: 11:11:11.111"
public var debugDescription: String {
return description
}

View File

@ -1,35 +1,44 @@
import Foundation
/// `Weekday` represents a day of a week without time.
/// `Weekday` represents a day of a week.
public enum Weekday: Int {
case sunday = 1, monday, tuesday, wednesday, thursday, friday, saturday
/// A Boolean value indicating whether today is the weekday.
public var isToday: Bool {
return Calendar.standard.dateComponents(in: .current, from: Date()).weekday == rawValue
}
/// Returns a dateComponenets of the weekday, using gregorian calender and
/// Returns dateComponenets of the weekday, using gregorian calender and
/// current time zone.
public func toDateComponents() -> DateComponents {
return DateComponents(calendar: Calendar.standard,
timeZone: TimeZone.current,
weekday: rawValue)
return DateComponents(
calendar: Calendar.gregorian,
timeZone: TimeZone.current,
weekday: rawValue
)
}
}
extension Date {
/// Returns a boolean value indicating whether this day is the weekday.
public func `is`(_ weekday: Weekday) -> Bool {
return Calendar.gregorian.component(.weekday, from: self) == weekday.rawValue
}
}
extension Weekday: CustomStringConvertible {
/// A textual representation of this weekday.
///
/// Weekday: Friday
public var description: String {
return "Weekday: \(Calendar.standard.weekdaySymbols[rawValue - 1])"
return "Weekday: \(Calendar.gregorian.weekdaySymbols[rawValue - 1])"
}
}
extension Weekday: CustomDebugStringConvertible {
/// A textual representation of this weekday for debugging.
///
/// Weekday: Friday
public var debugDescription: String {
return description
}

View File

@ -0,0 +1,19 @@
//
// Log.swift
// Schedule
//
// Created by Quentin Jin on 2019/3/12.
// Copyright © 2019 Schedule. All rights reserved.
//
import Foundation
let fmt = ISO8601DateFormatter()
func Log(_ t: Any) {
let now = fmt.string(from: Date())
let thread = Thread.isMainThread ? "main" : "background"
print("\(now) [\(thread)] -> \(t)")
}

View File

@ -0,0 +1,34 @@
//
// main.swift
// Schedule
//
// Created by Quentin Jin on 2019/3/12.
// Copyright © 2019 Schedule. All rights reserved.
//
import Foundation
import Schedule
Log("Wake up")
let t1 = Plan.after(1.second).do {
Log("1 second passed!")
}
let t2 = Plan.after(1.minute, repeating: 0.5.seconds).do {
Log("Ping!")
}
let t3 = Plan.every("one minute and ten seconds").do {
Log("One minute and ten seconds have elapsed!")
}
let t4 = Plan.every(.monday, .tuesday, .wednesday, .thursday, .friday).at(6, 50).do {
Log("Get up!")
}
let t5 = Plan.every(.june(14)).at("08:59:59 am").do {
Log("Happy birthday!")
}
RunLoop.current.run()

View File

@ -3,24 +3,28 @@ import XCTest
final class AtomicTests: XCTestCase {
func testSnapshot() {
let i = Atomic<Int>(1)
XCTAssertEqual(i.snapshot(), 1)
}
func testRead() {
let atom = Atomic<Int>(1)
atom.read {
let i = Atomic<Int>(1)
i.read {
XCTAssertEqual($0, 1)
}
}
func testWrite() {
let atom = Atomic<Int>(1)
atom.write {
$0 = 2
}
atom.read {
XCTAssertEqual($0, 2)
let i = Atomic<Int>(1)
i.write {
$0 += 1
}
XCTAssertEqual(i.snapshot(), 2)
}
static var allTests = [
("testSnapshot", testSnapshot),
("testRead", testRead),
("testWrite", testWrite)
]

View File

@ -1,78 +0,0 @@
import XCTest
@testable import Schedule
final class BucketTests: XCTestCase {
typealias Fn = () -> Int
func testBucketKey() {
let key = BucketKey(underlying: 0)
XCTAssertEqual(key.increased(), BucketKey(underlying: 1))
}
func testAdd() {
var bucket = Bucket<Fn>()
bucket.add { 1 }
bucket.add { 2 }
bucket.add { 3 }
XCTAssertEqual(bucket.count, 3)
}
func testRemove() {
var bucket = Bucket<Fn>()
let k1 = bucket.add { 1 }
let k2 = bucket.add { 2 }
let fn1 = bucket.removeElement(for: k1)
XCTAssertNotNil(fn1)
let fn2 = bucket.removeElement(for: k2)
XCTAssertNotNil(fn2)
XCTAssertNil(bucket.removeElement(for: k2.increased()))
bucket.removeAll()
XCTAssertEqual(bucket.count, 0)
bucket.add { 3 }
XCTAssertEqual(bucket.count, 1)
}
func testGet() {
var bucket = Bucket<Fn>()
let k1 = bucket.add { 1 }
let k2 = bucket.add { 2 }
XCTAssertNotNil(bucket.element(for: k1))
XCTAssertNotNil(bucket.element(for: k2))
guard let fn1 = bucket.element(for: k1), let fn2 = bucket.element(for: k2) else {
XCTFail()
return
}
XCTAssertEqual(fn1(), 1)
XCTAssertEqual(fn2(), 2)
XCTAssertNil(bucket.element(for: k2.increased()))
}
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 = [
("testBucketKey", testBucketKey),
("testAdd", testAdd),
("testRemove", testRemove),
("testGet", testGet),
("testSequence", testSequence)
]
}

View File

@ -0,0 +1,92 @@
import XCTest
@testable import Schedule
final class CabinetTests: XCTestCase {
typealias Fn = () -> Int
func testCabinetKey() {
let key = CabinetKey(underlying: 0)
XCTAssertEqual(key.increased(), CabinetKey(underlying: 1))
}
func testAppend() {
var cabinet = Cabinet<Fn>()
let k1 = cabinet.append { 1 }
let k2 = cabinet.append { 2 }
XCTAssertEqual(k1.increased(), k2)
XCTAssertEqual(cabinet.count, 2)
}
func testGet() {
var cabinet = Cabinet<Fn>()
let k1 = cabinet.append { 1 }
let k2 = cabinet.append { 2 }
guard
let fn1 = cabinet.get(k1),
let fn2 = cabinet.get(k2)
else {
XCTFail()
return
}
XCTAssertEqual(fn1(), 1)
XCTAssertEqual(fn2(), 2)
XCTAssertNil(cabinet.get(k2.increased()))
}
func testDelete() {
var cabinet = Cabinet<Fn>()
let k1 = cabinet.append { 1 }
let k2 = cabinet.append { 2 }
XCTAssertEqual(cabinet.count, 2)
let fn1 = cabinet.delete(k1)
XCTAssertNotNil(fn1)
let fn2 = cabinet.delete(k2)
XCTAssertNotNil(fn2)
XCTAssertEqual(cabinet.count, 0)
XCTAssertNil(cabinet.delete(k2.increased()))
}
func testClear() {
var cabinet = Cabinet<Fn>()
cabinet.append { 1 }
cabinet.append { 2 }
XCTAssertEqual(cabinet.count, 2)
cabinet.clear()
XCTAssertEqual(cabinet.count, 0)
}
func testSequence() {
var cabinet = Cabinet<Fn>()
cabinet.append { 0 }
cabinet.append { 1 }
cabinet.append { 2 }
var i = 0
for fn in cabinet {
XCTAssertEqual(fn(), i)
i += 1
}
}
static var allTests = [
("testCabinetKey", testCabinetKey),
("testAppend", testAppend),
("testGet", testGet),
("testDelete", testDelete),
("testClear", testClear),
("testSequence", testSequence)
]
}

View File

@ -22,9 +22,9 @@ final class DateTimeTests: XCTestCase {
XCTAssertTrue(1.1.second.isLonger(than: 1.0.second))
XCTAssertTrue(3.days.isShorter(than: 1.week))
XCTAssertEqual(Interval.longest(1.hour, 1.day, 1.week), 1.week)
XCTAssertEqual(Interval.longest([]), .init(nanoseconds: 0))
XCTAssertEqual(Interval.longest([]), nil)
XCTAssertEqual(Interval.shortest(1.hour, 59.minutes, 2999.seconds), 2999.seconds)
XCTAssertEqual(Interval.shortest([]), .init(nanoseconds: 0))
XCTAssertEqual(Interval.shortest([]), nil)
XCTAssertEqual(1.second * 60, 1.minute)
XCTAssertEqual(59.minutes + 60.seconds, 1.hour)
@ -35,7 +35,7 @@ final class DateTimeTests: XCTestCase {
XCTAssertEqual(-(1.second), (-1).second)
let i1 = Interval(seconds: 24 * 60 * 60)
XCTAssertEqual(1.nanosecond * i1.nanoseconds, 1.day)
XCTAssertEqual(1.nanosecond * i1.asNanoseconds(), 1.day)
XCTAssertEqual(2.microsecond * i1.asMicroseconds(), 2.days)
XCTAssertEqual(3.millisecond * i1.asMilliseconds(), 3.days)
XCTAssertEqual(4.second * i1.asSeconds(), 4.days)
@ -52,6 +52,9 @@ final class DateTimeTests: XCTestCase {
}
func testMonthday() {
let d = Date(year: 2019, month: 1, day: 1)
XCTAssertTrue(d.is(.january(1)))
XCTAssertEqual(Monthday.january(1).toDateComponents().month, 1)
XCTAssertEqual(Monthday.february(1).toDateComponents().month, 2)
XCTAssertEqual(Monthday.march(1).toDateComponents().month, 3)
@ -110,6 +113,14 @@ final class DateTimeTests: XCTestCase {
XCTAssertTrue(i.isAlmostEqual(to: (0.456.second.nanoseconds).nanoseconds, leeway: 0.001.seconds))
}
let components = t1?.toDateComponents()
XCTAssertEqual(components?.hour, 11)
XCTAssertEqual(components?.minute, 12)
XCTAssertEqual(components?.second, 13)
if let i = components?.nanosecond?.nanoseconds {
XCTAssertTrue(i.isAlmostEqual(to: (0.456.second.nanoseconds).nanoseconds, leeway: 0.001.seconds))
}
let t2 = Time("11 pm")
XCTAssertNotNil(t2)
XCTAssertEqual(t2?.hour, 23)
@ -121,10 +132,14 @@ final class DateTimeTests: XCTestCase {
let t4 = Time("schedule")
XCTAssertNil(t4)
XCTAssertEqual(Time(hour: 1)!.intervalSinceZeroClock, 1.hour)
XCTAssertEqual(Time(hour: 1)!.intervalSinceStartOfDay, 1.hour)
}
func testWeekday() {
// Be careful the time zone problem.
let d = Date(year: 2019, month: 1, day: 1)
XCTAssertTrue(d.is(.tuesday))
XCTAssertEqual(Weekday.monday.toDateComponents().weekday!, 2)
}

View File

@ -7,23 +7,23 @@ final class DeinitObserverTests: XCTestCase {
func testObserver() {
var i = 0
let b0 = {
var fn = {
let obj = NSObject()
DeinitObserver.observe(obj, onDeinit: {
DeinitObserver.observe(obj) {
i += 1
})
}
}
b0()
fn()
XCTAssertEqual(i, 1)
let b1 = {
fn = {
let obj = NSObject()
let observer = DeinitObserver.observe(obj, onDeinit: {
let observer = DeinitObserver.observe(obj) {
i += 1
})
observer.cancel()
}
observer.invalidate()
}
b1()
fn()
XCTAssertEqual(i, 1)
}

View File

@ -4,26 +4,30 @@ import XCTest
final class ExtensionsTests: XCTestCase {
func testClampedToInt() {
let a: Double = 0.1
XCTAssertEqual(a.clampedToInt(), 0)
let a = Double(Int.max) + 1
XCTAssertEqual(a.clampedToInt(), Int.max)
let b = Double(Int.min) - 1
XCTAssertEqual(b.clampedToInt(), Int.min)
}
func testClampedAdding() {
let a: Int = 1
let b: Int = .max
XCTAssertEqual(a.clampedAdding(b), Int.max)
let i = Int.max
XCTAssertEqual(i.clampedAdding(1), Int.max)
}
func testClampedSubtracting() {
let a: Int = .min
let b: Int = 1
XCTAssertEqual(a.clampedSubtracting(b), Int.min)
let i = Int.min
XCTAssertEqual(i.clampedSubtracting(1), Int.min)
}
func testStart() {
let start = Date().start
let components = start.dateComponents
guard let h = components.hour, let m = components.minute, let s = components.second else {
func testStartOfToday() {
let components = Date().startOfToday.dateComponents
guard
let h = components.hour,
let m = components.minute,
let s = components.second
else {
XCTFail()
return
}
@ -36,6 +40,6 @@ final class ExtensionsTests: XCTestCase {
("testClampedToInt", testClampedToInt),
("testClampedAdding", testClampedAdding),
("testClampedSubtracting", testClampedSubtracting),
("testStart", testStart)
("testStartOfToday", testStartOfToday)
]
}

View File

@ -4,15 +4,21 @@ import Foundation
extension Date {
var dateComponents: DateComponents {
return Calendar.standard.dateComponents(in: TimeZone.current, from: self)
return Calendar.gregorian.dateComponents(in: TimeZone.current, from: self)
}
init(year: Int, month: Int, day: Int, hour: Int = 0, minute: Int = 0, second: Int = 0, nanosecond: Int = 0) {
let components = DateComponents(calendar: Calendar.standard,
timeZone: TimeZone.current,
year: year, month: month, day: day,
hour: hour, minute: minute, second: second,
nanosecond: nanosecond)
init(
year: Int, month: Int, day: Int,
hour: Int = 0, minute: Int = 0, second: Int = 0,
nanosecond: Int = 0
) {
let components = DateComponents(
calendar: Calendar.gregorian,
timeZone: TimeZone.current,
year: year, month: month, day: day,
hour: hour, minute: minute, second: second,
nanosecond: nanosecond
)
self = components.date ?? Date.distantPast
}
}
@ -34,14 +40,17 @@ extension Double {
extension Sequence where Element == Interval {
func isAlmostEqual<S>(to sequence: S, leeway: Interval) -> Bool where S: Sequence, S.Element == Element {
var i0 = self.makeIterator()
var i1 = sequence.makeIterator()
while let l = i0.next(), let r = i1.next() {
if (l - r).magnitude > leeway.magnitude {
var it0 = self.makeIterator()
var it1 = sequence.makeIterator()
while let l = it0.next(), let r = it1.next() {
if l.isAlmostEqual(to: r, leeway: leeway) {
continue
} else {
return false
}
}
return i0.next() == i1.next()
return it0.next() == it1.next()
}
}
@ -54,14 +63,16 @@ extension Plan {
extension DispatchQueue {
func async(after delay: Interval, execute body: @escaping () -> Void) {
asyncAfter(wallDeadline: .now() + delay.asSeconds(), execute: body)
func async(after interval: Interval, execute body: @escaping () -> Void) {
asyncAfter(wallDeadline: .now() + interval.asSeconds(), execute: body)
}
static func `is`(_ queue: DispatchQueue) -> Bool {
let key = DispatchSpecificKey<()>()
queue.setSpecific(key: key, value: ())
defer { queue.setSpecific(key: key, value: nil) }
return DispatchQueue.getSpecific(key: key) != nil
}
}

View File

@ -18,9 +18,7 @@ final class PlanTests: XCTestCase {
let d3 = d2 + intervals[3]
let s2 = Plan.of(d0, d1, d2, d3)
let s3 = Plan.from([d0, d1, d2, d3])
XCTAssertTrue(s2.makeIterator().isAlmostEqual(to: intervals, leeway: leeway))
XCTAssertTrue(s3.makeIterator().isAlmostEqual(to: intervals, leeway: leeway))
let longTime = (100 * 365).days
XCTAssertTrue(Plan.distantPast.makeIterator().next()!.isLonger(than: longTime))

View File

@ -8,61 +8,83 @@ final class TaskCenterTests: XCTestCase {
return Plan.never.do { }
}
var defaultCenter: TaskCenter {
var center: TaskCenter {
return TaskCenter.default
}
func testDefault() {
let task = makeTask()
XCTAssertTrue(defaultCenter.allTasks.contains(task))
defaultCenter.clear()
XCTAssertTrue(center.allTasks.contains(task))
center.clear()
}
func testAdd() {
let centerA = TaskCenter()
let c = TaskCenter()
let task = makeTask()
centerA.add(task)
XCTAssertEqual(center.allTasks.count, 1)
XCTAssertEqual(defaultCenter.allTasks.count, 0)
XCTAssertEqual(centerA.allTasks.count, 1)
c.add(task)
XCTAssertEqual(center.allTasks.count, 0)
XCTAssertEqual(c.allTasks.count, 1)
centerA.add(task)
XCTAssertEqual(centerA.allTasks.count, 1)
c.add(task)
XCTAssertEqual(c.allTasks.count, 1)
center.clear()
}
func testRemove() {
let task = makeTask()
defaultCenter.remove(task)
XCTAssertFalse(defaultCenter.allTasks.contains(task))
center.remove(task)
XCTAssertFalse(center.allTasks.contains(task))
}
func testTag() {
let task = makeTask()
let tag0 = UUID().uuidString
task.addTag(tag0)
XCTAssertTrue(defaultCenter.tasksWithTag(tag0).contains(task))
let tag = UUID().uuidString
let tag1 = UUID().uuidString
task.addTag(tag1)
XCTAssertTrue(defaultCenter.tasksWithTag(tag1).contains(task))
center.addTag(tag, to: task)
XCTAssertTrue(center.tasksForTag(tag).contains(task))
XCTAssertTrue(center.tagsForTask(task).contains(tag))
task.removeTag(tag0)
XCTAssertFalse(defaultCenter.tasksWithTag(tag0).contains(task))
center.removeTag(tag, from: task)
XCTAssertFalse(center.tasksForTag(tag).contains(task))
XCTAssertFalse(center.tagsForTask(task).contains(tag))
defaultCenter.clear()
center.clear()
}
func testCount() {
XCTAssertEqual(defaultCenter.allTasks.count, 0)
func testAll() {
let task = makeTask()
XCTAssertEqual(defaultCenter.allTasks.count, 1)
_ = task
let tag = UUID().uuidString
defaultCenter.clear()
center.addTag(tag, to: task)
XCTAssertEqual(center.allTags, [tag])
XCTAssertEqual(center.allTasks, [task])
center.clear()
}
func testOperation() {
let task = makeTask()
let tag = UUID().uuidString
center.addTag(tag, to: task)
center.suspendByTag(tag)
XCTAssertEqual(task.suspensions, 1)
center.resumeByTag(tag)
XCTAssertEqual(task.suspensions, 0)
center.cancelByTag(tag)
XCTAssertTrue(task.isCancelled)
center.clear()
}
func testWeak() {
@ -71,7 +93,7 @@ final class TaskCenterTests: XCTestCase {
}
block()
XCTAssertEqual(defaultCenter.allTasks.count, 0)
XCTAssertEqual(center.allTasks.count, 0)
}
static var allTests = [
@ -79,7 +101,8 @@ final class TaskCenterTests: XCTestCase {
("testAdd", testAdd),
("testRemove", testRemove),
("testTag", testTag),
("testCount", testCount),
("testAll", testAll),
("testOperation", testOperation),
("testWeak", testWeak)
]
}

View File

@ -61,16 +61,6 @@ final class TaskTests: XCTestCase {
XCTAssertEqual(task1.suspensions, 3)
task1.resume()
XCTAssertEqual(task1.suspensions, 2)
let tag = UUID().uuidString
let task2 = Plan.distantFuture.do { }
task2.addTag(tag)
Task.suspend(byTag: tag)
XCTAssertEqual(task2.suspensions, 1)
Task.resume(byTag: tag)
XCTAssertEqual(task2.suspensions, 0)
Task.cancel(byTag: tag)
XCTAssertTrue(task2.isCancelled)
}
func testAddAndRemoveActions() {
@ -93,23 +83,6 @@ final class TaskTests: XCTestCase {
XCTAssertEqual(task.countOfActions, 0)
}
func testAddAndRemoveTags() {
let task = Plan.never.do { }
let tagA = UUID().uuidString
let tagB = UUID().uuidString
let tagC = UUID().uuidString
task.addTag(tagA)
task.addTags(tagB, tagC)
XCTAssertTrue(task.tags.contains(tagA))
XCTAssertTrue(task.tags.contains(tagC))
task.removeTag(tagA)
XCTAssertFalse(task.tags.contains(tagA))
task.removeTags(tagB, tagC)
XCTAssertFalse(task.tags.contains(tagB))
XCTAssertFalse(task.tags.contains(tagC))
task.cancel()
}
func testReschedule() {
let e = expectation(description: "testReschedule")
var i = 0
@ -171,7 +144,6 @@ final class TaskTests: XCTestCase {
("testDispatchQueue", testDispatchQueue),
("testThread", testThread),
("testAddAndRemoveActions", testAddAndRemoveActions),
("testAddAndRemoveTags", testAddAndRemoveTags),
("testReschedule", testReschedule),
("testHost", testHost),
("testLifetime", testLifetime)

View File

@ -8,7 +8,7 @@ public func allTests() -> [XCTestCaseEntry] {
testCase(TaskCenterTests.allTests),
testCase(TaskTests.allTests),
testCase(AtomicTests.allTests),
testCase(BucketTests.allTests),
testCase(CabinetTests.allTests),
testCase(CalendarTests.allTests),
testCase(ExtensionsTests.allTests)
]