commit
0d16c26cdd
|
@ -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>
|
|
@ -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"])
|
||||
]
|
||||
)
|
||||
|
|
|
@ -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!")
|
||||
}
|
||||
|
|
@ -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>
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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
|
@ -4,4 +4,4 @@
|
|||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
</Workspace>
|
|
@ -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>
|
|
@ -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">
|
|
@ -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>
|
|
@ -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>
|
|
@ -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">
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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?()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)" +
|
||||
|
|
|
@ -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() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)")
|
||||
}
|
|
@ -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()
|
|
@ -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)
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
]
|
||||
}
|
|
@ -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)
|
||||
]
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue