Update README
This commit is contained in:
parent
328439049a
commit
3100e6b8fa
233
README.md
233
README.md
|
@ -1,17 +1,17 @@
|
|||
# Schedule([简体中文](README.zh_cn.md))
|
||||
|
||||
<p align="center">
|
||||
|
||||
[](https://travis-ci.org/jianstm/Schedule)
|
||||
[](https://codecov.io/gh/jianstm/Schedule)
|
||||
<a href="https://github.com/jianstm/Schedule/releases">
|
||||
<img src="https://img.shields.io/github/tag/jianstm/Schedule.svg">
|
||||
<img src="https://img.shields.io/cocoapods/v/Schedule.svg">
|
||||
</a>
|
||||
<img src="https://img.shields.io/travis/jianstm/Schedule.svg">
|
||||
<img src="https://img.shields.io/codecov/c/github/jianstm/Schedule.svg">
|
||||
<img src="https://img.shields.io/badge/support-CocoaPods%20%7C%20Carthage%20%7C%20SwiftPM-brightgreen.svg">
|
||||
<img src="https://img.shields.io/badge/platform-iOS%20%7C%20macOS%20%7C%20watchOS%20%7C%20tvOS%20%7C%20Linux-lightgrey.svg">
|
||||
<img src="https://img.shields.io/cocoapods/p/Schedule.svg">
|
||||
<img src="https://img.shields.io/github/license/jianstm/Schedule.svg">
|
||||
</p>
|
||||
|
||||
Schedule is a lightweight timed tasks scheduler for Swift. It allows you run timed tasks using an incredibly human-friendly syntax.
|
||||
Schedule is a timing tasks scheduler written in Swift. It allows you run timing tasks with elegant and intuitive syntax.
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/demo.png" width="700">
|
||||
|
@ -19,17 +19,13 @@ Schedule is a lightweight timed tasks scheduler for Swift. It allows you run tim
|
|||
|
||||
## Features
|
||||
|
||||
- [x] Variety of Scheduling Rules
|
||||
- [x] Suspend, Resume, Cancel
|
||||
- [x] Reschedule
|
||||
- [x] Tag-based Task Management
|
||||
- [x] Child-action Add/Remove
|
||||
- [x] Natural Language Parse
|
||||
- [x] Atomic Operation
|
||||
- [x] Full Control Over Life Cycle
|
||||
- [x] 95%+ Test Coverage
|
||||
- [x] Complete Documentation(All Public Types & Methods)
|
||||
- [x] Linux Support(Tested on Ubuntu 16.04)
|
||||
- [x] Elegant and intuitive API
|
||||
- [x] Rich preset rules
|
||||
- [x] Powerful management mechanism
|
||||
- [x] Detailed execution history
|
||||
- [x] Thread safe
|
||||
- [x] Complete documentation
|
||||
- [x] ~100%+ test coverage
|
||||
|
||||
### Why You Should Use Schedule
|
||||
|
||||
|
@ -37,22 +33,19 @@ Schedule is a lightweight timed tasks scheduler for Swift. It allows you run tim
|
|||
| --- | :---: | :---: | :---: |
|
||||
| ⏰ Interval-based Schedule | ✓ | ✓ | ✓ |
|
||||
| 📆 Date-based Schedule | ✓ | | ✓ |
|
||||
| 🌈 Mixing Rules Schedule | | | ✓ |
|
||||
| 🌈 Combined Plan Schedule | | | ✓ |
|
||||
| 🗣️ Natural Language Parse | | | ✓ |
|
||||
| 🏷 Batch Task Management | | | ✓ |
|
||||
| 📝 Execution Record | | | ✓ |
|
||||
| 🎡 Plan Reset | | ✓ | ✓ |
|
||||
| 🚦 Suspend, Resume, Cancel | | ✓ | ✓ |
|
||||
| 🎡 Reschedule | | ✓ | ✓ |
|
||||
| 🏷 Tag-based Task Management | | | ✓ |
|
||||
| 🍰 Child-action Add/Remove | | | ✓ |
|
||||
| 📝 Natural Language Parse | | | ✓ |
|
||||
| 🚔 Atomic Operation | | | ✓ |
|
||||
| 🕕 Lifecycly Bind | | | ✓ |
|
||||
| 🚀 Realtime Timeline Inspect | | | ✓ |
|
||||
| 🎯 Lifetime Specify | | | ✓ |
|
||||
| 🍰 Child-action | | | ✓ |
|
||||
|
||||
## Usage
|
||||
|
||||
### Overview
|
||||
|
||||
Scheduling a task has never been so simple and intuitive, all you have to do is:
|
||||
Scheduling a task has never been so elegant and intuitive, all you have to do is:
|
||||
|
||||
```swift
|
||||
// 1. define your plan:
|
||||
|
@ -68,7 +61,9 @@ let task = plan.do {
|
|||
|
||||
#### Interval-based Schedule
|
||||
|
||||
Schedule uses a self-defined type `Interval` to configure timed tasks, so you don't have to worry about extensions of built-in type polluting your namespace. The smooth constructors make the configuration like a comfortable conversation:
|
||||
The running mechanism of Schedule is based on `Plan`, and `Plan` is actually a sequence of `Interval`.
|
||||
|
||||
Schedule makes `Plan` definitions more elegant and intuitive by extending `Int` and `Double`. Also, because `Interval` is a built-in type of Schedule, you don't have to worry about it being polluting your namespace.
|
||||
|
||||
```swift
|
||||
let t1 = Plan.every(1.second).do { }
|
||||
|
@ -80,10 +75,10 @@ let t3 = Plan.of(1.second, 2.minutes, 3.hours).do { }
|
|||
|
||||
#### Date-based Schedule
|
||||
|
||||
Configuring date-based timing tasks is the same, Schedule defines all the commonly used date time types, trying to make your writing experience intuitive and smooth::
|
||||
Configuring date-based `Plan` is the same, with the expressive Swift syntax, Schedule makes your code look like a fluent conversation.
|
||||
|
||||
```swift
|
||||
let t1 = Plan.at(when).do { }
|
||||
let t1 = Plan.at(date).do { }
|
||||
|
||||
let t2 = Plan.every(.monday, .tuesday).at("9:00:00").do { }
|
||||
|
||||
|
@ -96,7 +91,7 @@ let t5 = Plan.of(date0, date1, date2).do { }
|
|||
|
||||
#### Natural Language Parse
|
||||
|
||||
In addition, Schedule also supports basic natural language parsing, which greatly improves the readability of your code:
|
||||
In addition, Schedule also supports simple natural language parsing.
|
||||
|
||||
```swift
|
||||
let t1 = Plan.every("one hour and ten minutes").do { }
|
||||
|
@ -109,9 +104,9 @@ Period.registerQuantifier("many", for: 100 * 1000)
|
|||
let t4 = Plan.every("many days").do { }
|
||||
```
|
||||
|
||||
#### Mixing Rules Schedule
|
||||
#### Combined Plan Schedule
|
||||
|
||||
Schedule provides several collection operators, this means you can use them to customize your awesome rules:
|
||||
Schedule provides several basic collection operators, which means you can use them to customize your own powerful plans.
|
||||
|
||||
```swift
|
||||
/// Concat
|
||||
|
@ -139,34 +134,11 @@ let p7 = P.every(.monday).at(11, 12)
|
|||
let p8 = p7.until(date)
|
||||
```
|
||||
|
||||
### Creation
|
||||
|
||||
#### Parasitism
|
||||
|
||||
Schedule provides a parasitic mechanism, that allows you to handle one of the most common scenarios in a more elegant way:
|
||||
|
||||
```swift
|
||||
Plan.every(1.second).do(host: self) {
|
||||
// do something, and cancel the task when host is deallocated.
|
||||
// this's very useful when you want to bind a task's lifetime to a controller.
|
||||
}
|
||||
```
|
||||
|
||||
#### RunLoop
|
||||
|
||||
The task will be executed on the current thread by default, and its implementation is based on RunLoop. So you need to ensure that the current thread has a RunLoop available. If the task is created on a child thread, you may need to run `RunLoop.current.run()`.
|
||||
|
||||
By default, Task will be added to `.common` mode, you can specify another mode when creating a task:
|
||||
|
||||
```swift
|
||||
let task = Plan.every(1.second).do(mode: .default) {
|
||||
print("on default mode...")
|
||||
}
|
||||
```
|
||||
### Management
|
||||
|
||||
#### DispatchQueue
|
||||
|
||||
You can use `queue` to specify which DispatchQueue the task will be dispatched to. In this case, the execution of the task is no longer dependent on RunLoop, you can use it safely on a child thread:
|
||||
When calling `plan.do` to dispatch a timing task, you can use `queue` to specify which `DispatchQueue` the task will be dispatched to when the time is up. This operation does not rely on `RunLoop` like `Timer`, so you can call it on any thread.
|
||||
|
||||
```swift
|
||||
Plan.every(1.second).do(queue: .global()) {
|
||||
|
@ -174,47 +146,35 @@ Plan.every(1.second).do(queue: .global()) {
|
|||
}
|
||||
```
|
||||
|
||||
### Management
|
||||
|
||||
You can `suspend`, `resume`, `cancel` a task.
|
||||
#### RunLoop
|
||||
|
||||
If `queue` is not specified, Schedule will use `RunLoop` to dispatch the task, at which point the task will execute on the current thread. **Please note**, like `Timer`, which is also based on `RunLoop`, you need to ensure that the current thread has an **available** `RunLoop`. By default, the task will be added to `.common` mode, you can specify another mode when creating the task.
|
||||
|
||||
```swift
|
||||
let task = Plan.every(1.minute).do { }
|
||||
|
||||
// will increase task's suspensions
|
||||
task.suspend()
|
||||
|
||||
// will decrease task's suspensions,
|
||||
// but don't worry about excessive resumptions, I will handle these for you~
|
||||
task.resume()
|
||||
|
||||
// cancel task, this will remove task from the internal holder,
|
||||
// in other words, will reduce task's reference count,
|
||||
// if there are no other holders, task will be released.
|
||||
task.cancel()
|
||||
let task = Plan.every(1.second).do(mode: .default) {
|
||||
print("on default mode...")
|
||||
}
|
||||
```
|
||||
|
||||
#### Action
|
||||
#### Timeline
|
||||
|
||||
You can add more actions to a task and delete them at any time you want:
|
||||
You can observe the execution record of the task in real time using the following properties.
|
||||
|
||||
```swift
|
||||
let dailyTask = Plan.every(1.day)
|
||||
dailyTask.addAction {
|
||||
print("open eyes")
|
||||
}
|
||||
dailyTask.addAction {
|
||||
print("get up")
|
||||
}
|
||||
let key = dailyTask.addAction {
|
||||
print("take a shower")
|
||||
}
|
||||
dailyTask.removeAction(byKey: key)
|
||||
task.creationDate
|
||||
|
||||
task.executionHistory
|
||||
|
||||
task.firstExecutionDate
|
||||
task.lastExecutionDate
|
||||
|
||||
task.estimatedNextExecutionDate
|
||||
```
|
||||
|
||||
#### TaskCenter & Tag
|
||||
|
||||
Tasks are automatically added to `TaskCenter.default` when they are created. You can organize tasks using tags and task center.
|
||||
Tasks are automatically added to `TaskCenter.default` by default,you can organize them using tags and task center.
|
||||
|
||||
```swift
|
||||
let plan = Plan.every(1.day)
|
||||
|
@ -231,40 +191,47 @@ TaskCenter.default.cancel(byTag: "log")
|
|||
TaskCenter.default.clear()
|
||||
|
||||
let myCenter = TaskCenter()
|
||||
myCenter.add(task0) // will remove task0 from default center.
|
||||
myCenter.add(task0)
|
||||
```
|
||||
|
||||
#### Timeline
|
||||
|
||||
You can inspect the timeline of a task in real time:
|
||||
### Suspend,Resume, Cancel
|
||||
|
||||
You can `suspend`, `resume`, `cancel` a task.
|
||||
|
||||
```swift
|
||||
let timeline = task.timeline
|
||||
print(timeline.initialization)
|
||||
print(timeline.firstExecution)
|
||||
print(timeline.lastExecution)
|
||||
print(timeline.estimatedNextExecution)
|
||||
let task = Plan.every(1.minute).do { }
|
||||
|
||||
// will increase task's suspensionCount
|
||||
task.suspend()
|
||||
|
||||
// will decrease task's suspensionCount,
|
||||
// but don't worry about excessive resumptions, I will handle these for you~
|
||||
task.resume()
|
||||
|
||||
// will clear task's suspensionCount
|
||||
// a canceled task can't do anything, event if it is set to a new plan.
|
||||
task.cancel()
|
||||
```
|
||||
|
||||
#### Lifetime
|
||||
#### Action
|
||||
|
||||
And specify the lifetime of task:
|
||||
You can add more actions to a task and remove them at any time you want:
|
||||
|
||||
```swift
|
||||
// will be cancelled after 10 hours.
|
||||
task.setLifetime(10.hours)
|
||||
|
||||
// will add 1 hour to tasks lifetime
|
||||
task.addLifetime(1.hour)
|
||||
|
||||
task.restOfLifetime == 11.hours
|
||||
let dailyTask = Plan.every(1.day)
|
||||
dailyTask.addAction {
|
||||
print("open eyes")
|
||||
}
|
||||
dailyTask.addAction {
|
||||
print("get up")
|
||||
}
|
||||
let key = dailyTask.addAction {
|
||||
print("take a shower")
|
||||
}
|
||||
dailyTask.removeAction(byKey: key)
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
- iOS 10.0+ / macOS 10.14+ / tvOS 10.0+ / watchOS 3.0+
|
||||
- Linux(Tested on Ubuntu 16.04)
|
||||
|
||||
## Installation
|
||||
|
||||
### CocoaPods
|
||||
|
@ -274,44 +241,44 @@ task.restOfLifetime == 11.hours
|
|||
use_frameworks!
|
||||
|
||||
target 'YOUR_TARGET_NAME' do
|
||||
pod 'Schedule', '~> 1.0'
|
||||
pod 'Schedule', '~> 2.0'
|
||||
end
|
||||
```
|
||||
|
||||
### Carthage
|
||||
|
||||
```ruby
|
||||
github "jianstm/Schedule" ~> 1.0
|
||||
```
|
||||
github "jianstm/Schedule" ~> 2.0
|
||||
```
|
||||
|
||||
### Swift Package Manager
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/jianstm/Schedule", .upToNextMajor(from: "1.0.0"))
|
||||
.package(
|
||||
url: "https://github.com/jianstm/Schedule", .upToNextMajor(from: "2.0.0")
|
||||
)
|
||||
]
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Like **Schedule**? Thanks!!!
|
||||
|
||||
At the same time, I need your help~
|
||||
|
||||
### Finding Bugs
|
||||
|
||||
Schedule is just getting started. If you could help the Schedule find or fix potential bugs, I would be grateful!
|
||||
|
||||
### New Features
|
||||
|
||||
Have some awesome ideas? Feel free to open an issue or submit your pull request directly!
|
||||
|
||||
### Documentation improvements.
|
||||
|
||||
Improvements to README and documentation are welcome at all times, whether typos or my lame English, 🤣.
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
Inspired by Dan Bader's [schedule](https://github.com/dbader/schedule)!
|
||||
|
||||
## Contributing
|
||||
|
||||
Like **Schedule**? Thank you so much! At the same time, I need your help:
|
||||
|
||||
### Finding Bugs
|
||||
|
||||
Schedule is just getting started, it is difficult to say how far the project is from bug free. If you could help the Schedule find or fix bugs that haven't been discovered yet, I would appreciate it!
|
||||
|
||||
### New Features
|
||||
|
||||
Any awesome ideas? Feel free to open an issue or submit your pull request directly!
|
||||
|
||||
### Documentation improvements.
|
||||
|
||||
Improvements to README and documentation are welcome at all times, whether typos or my lame English. For users, the documentation is sometimes much more important than the specific code implementation.
|
||||
|
||||
### Share
|
||||
|
||||
The more users the project has, the more robust the project will become, so, star! fork! and tell your friends!
|
||||
|
|
236
README.zh_cn.md
236
README.zh_cn.md
|
@ -1,17 +1,17 @@
|
|||
# Schedule
|
||||
|
||||
<p align="center">
|
||||
|
||||
[](https://travis-ci.org/jianstm/Schedule)
|
||||
[](https://codecov.io/gh/jianstm/Schedule)
|
||||
<a href="https://github.com/jianstm/Schedule/releases">
|
||||
<img src="https://img.shields.io/github/tag/jianstm/Schedule.svg">
|
||||
<img src="https://img.shields.io/cocoapods/v/Schedule.svg">
|
||||
</a>
|
||||
<img src="https://img.shields.io/travis/jianstm/Schedule.svg">
|
||||
<img src="https://img.shields.io/codecov/c/github/jianstm/Schedule.svg">
|
||||
<img src="https://img.shields.io/badge/support-CocoaPods%20%7C%20Carthage%20%7C%20SwiftPM-brightgreen.svg">
|
||||
<img src="https://img.shields.io/badge/platform-iOS%20%7C%20macOS%20%7C%20watchOS%20%7C%20tvOS%20%7C%20Linux-lightgrey.svg">
|
||||
<img src="https://img.shields.io/cocoapods/p/Schedule.svg">
|
||||
<img src="https://img.shields.io/github/license/jianstm/Schedule.svg">
|
||||
</p>
|
||||
|
||||
Schedule 是一个轻量级的调度框架,它能让你用难以置信的友好语法执行定时任务。
|
||||
Schedule 是一个用 Swift 编写的定时任务调度器,它能让你用优雅、直观的语法执行定时任务。
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/demo.png" width="700">
|
||||
|
@ -19,40 +19,33 @@ Schedule 是一个轻量级的调度框架,它能让你用难以置信的友
|
|||
|
||||
## 功能
|
||||
|
||||
- [x] 多种调度规则
|
||||
- [x] 暂停、继续、取消
|
||||
- [x] 重置调度规则
|
||||
- [x] 基于 tag 的任务管理
|
||||
- [x] 添加、移除子动作
|
||||
- [x] 自然语言解析
|
||||
- [x] 原子操作
|
||||
- [x] 对生命周期的完全控制
|
||||
- [x] 95%+ 测试覆盖
|
||||
- [x] 完善的文档(所有 public 类型和方法)
|
||||
- [x] 支持 Linux(通过 Ubuntu 16.04 测试)
|
||||
- [x] 优雅,直观的 API
|
||||
- [x] 丰富的预置规则
|
||||
- [x] 强大的管理机制
|
||||
- [x] 细致的执行记录
|
||||
- [x] 线程安全
|
||||
- [x] 完整的文档
|
||||
- [x] ~100% 的测试覆盖
|
||||
|
||||
### 为什么你该用 Schedule
|
||||
### 为什么你该使用 Schedule,而不是……
|
||||
|
||||
| 功能 | Timer | DispatchSourceTimer | Schedule |
|
||||
| --- | :---: | :---: | :---: |
|
||||
| ⏰ 基于时间间隔调度 | ✓ | ✓ | ✓ |
|
||||
| 📆 基于日期调度 | ✓ | | ✓ |
|
||||
| 🌈 自定义规则调度 | | | ✓ |
|
||||
| 🌈 组合计划调度 | | | ✓ |
|
||||
| 🗣️ 自然语言解析 | | | ✓ |
|
||||
| 🏷 批任务管理 | | | ✓ |
|
||||
| 📝 执行记录 | | | ✓ |
|
||||
| 🎡 规则重置 | | ✓ | ✓ |
|
||||
| 🚦 暂停、继续、取消 | | ✓ | ✓ |
|
||||
| 🎡 重置规则 | | ✓ | ✓ |
|
||||
| 🏷 基于 tag 的任务管理 | | | ✓ |
|
||||
| 🍰 添加、移除子动作 | | | ✓ |
|
||||
| 📝 自然语言解析 | | | ✓ |
|
||||
| 🚔 原子操作 | | | ✓ |
|
||||
| 🕕 生命周期绑定 | | | ✓ |
|
||||
| 🚀 实时观察时间线 | | | ✓ |
|
||||
| 🏌 寿命设置 | | | ✓ |
|
||||
| 🍰 子动作 | | | ✓ |
|
||||
|
||||
## 用法
|
||||
|
||||
### 一瞥
|
||||
|
||||
调度一个定时任务从未如此简单直观,你要做的只有:
|
||||
调度一个定时任务从未如此优雅、直观,你只需要:
|
||||
|
||||
```swift
|
||||
// 1. 定义你的计划:
|
||||
|
@ -64,11 +57,13 @@ let task = plan.do {
|
|||
}
|
||||
```
|
||||
|
||||
### 规则
|
||||
### 计划
|
||||
|
||||
#### 基于时间间隔调度
|
||||
|
||||
Schedule 使用自定义的 `Interval` 类型来配置定时任务,你不必担心对内置类型的扩展会污染你的命名空间。流畅的构造方法让配置像一场舒服的对话:
|
||||
Schedule 的机制基于 `Plan`,而 `Plan` 的本质是一系列 `Interval`。
|
||||
|
||||
Schedule 通过扩展 `Int` 和 `Double` 让 `Plan` 的定义更加优雅、直观。同时,因为 `Interval` 是 Schedule 的内置类型,所以你不用担心这会对你的命名空间产生污染。
|
||||
|
||||
```swift
|
||||
let t1 = Plan.every(1.second).do { }
|
||||
|
@ -80,10 +75,10 @@ let t3 = Plan.of(1.second, 2.minutes, 3.hours).do { }
|
|||
|
||||
#### 基于日期调度
|
||||
|
||||
配置基于日期的调度同样如此,Schedule 定义了所有常用的日期类型,尽力让你的书写直观、流畅:
|
||||
定制基于日期的 `Plan` 同样如此,配合富有表现力的 Swift 语法,Schedule 让你的代码看起来就像一场流畅的对话。
|
||||
|
||||
```swift
|
||||
let t1 = Plan.at(when).do { }
|
||||
let t1 = Plan.at(date).do { }
|
||||
|
||||
let t2 = Plan.every(.monday, .tuesday).at("9:00:00").do { }
|
||||
|
||||
|
@ -96,7 +91,7 @@ let t5 = Plan.of(date0, date1, date2).do { }
|
|||
|
||||
#### 自然语言解析
|
||||
|
||||
除此之外,Schedule 还支持基础的自然语言解析,这大大增强了你的代码的可读性:
|
||||
除此之外,Schedule 还支持简单的自然语言解析。
|
||||
|
||||
```swift
|
||||
let t1 = Plan.every("one hour and ten minutes").do { }
|
||||
|
@ -109,9 +104,9 @@ Period.registerQuantifier("many", for: 100 * 1000)
|
|||
let t4 = Plan.every("many days").do { }
|
||||
```
|
||||
|
||||
#### 自定义规则调度
|
||||
#### 组合计划调度
|
||||
|
||||
Schedule 还提供了几个简单的集合操作符,这意味着你可以使用它们定制属于你的强大规则:
|
||||
Schedule 提供了几个基本的集合操作符,这意味着,你可以使用它们自由组合,定制属于你的强大规则。
|
||||
|
||||
```swift
|
||||
/// Concat
|
||||
|
@ -139,32 +134,11 @@ let p7 = P.every(.monday).at(11, 12)
|
|||
let p8 = p7.until(date)
|
||||
```
|
||||
|
||||
### 创建
|
||||
|
||||
#### 寄生
|
||||
|
||||
Schedule 提供了一种寄生机制,它让你可以以一种更优雅的方式处理 task 的生命周期:
|
||||
|
||||
```swift
|
||||
Plan.every(1.second).do(host: self) {
|
||||
// task 会在 host 被 deallocated 后自动被 cancel
|
||||
// 这在你想要把一个 task 的生命周期绑定到控制器上时非常有用
|
||||
}
|
||||
```
|
||||
|
||||
#### RunLoop
|
||||
|
||||
Task 默认会在当前线程上执行,它的实现依赖于 RunLoop,所以你需要保证当前线程有一个可用的 RunLoop。如果 task 的创建在子线程上,你可能需要执行 `RunLoop.current.run()`。默认情况下, task 会被添加到 `.common` mode 上,你可以在创建 task 时指定其它 mode:
|
||||
|
||||
```swift
|
||||
let task = Plan.every(1.second).do(mode: .default) {
|
||||
print("on default mode...")
|
||||
}
|
||||
```
|
||||
### 管理
|
||||
|
||||
#### DispatchQueue
|
||||
|
||||
你也可以使用 queue 来指定 task 会被派发到哪个 DispatchQueue 上,这时,task 的执行不再依赖于 RunLoop,意味着你可以放心地子线程上使用:
|
||||
调用 `plan.do` 来调度定时任务时,你可以使用 `queue` 来指定当时间到时,task 会被派发到哪个 `DispatchQueue` 上。这个操作不像 `Timer` 那样依赖 `RunLoop`,所以你可以在任意线程上使用它。
|
||||
|
||||
```swift
|
||||
let task = Plan.every(1.second).do(queue: .global()) {
|
||||
|
@ -172,7 +146,54 @@ let task = Plan.every(1.second).do(queue: .global()) {
|
|||
}
|
||||
```
|
||||
|
||||
### 管理
|
||||
#### RunLoop
|
||||
|
||||
如果没有指定 `queue`,Schedule 会使用 `RunLoop` 来调度 task,这时,task 会在当前线程上执行。**要注意**,和同样基于 `RunLoop` 的 `Timer` 一样,你需要保证当前线程有一个**可用**的 `RunLoop`。默认情况下, task 会被添加到 `.common` mode 上,你可以在创建 task 时指定其它 mode。
|
||||
|
||||
```swift
|
||||
let task = Plan.every(1.second).do(mode: .default) {
|
||||
print("on default mode...")
|
||||
}
|
||||
```
|
||||
|
||||
#### Timeline
|
||||
|
||||
你可以使用以下属性实时地观察 task 的执行记录。
|
||||
|
||||
```swift
|
||||
task.creationDate
|
||||
|
||||
task.executionHistory
|
||||
|
||||
task.firstExecutionDate
|
||||
task.lastExecutionDate
|
||||
|
||||
task.estimatedNextExecutionDate
|
||||
```
|
||||
|
||||
#### TaskCenter 和 Tag
|
||||
|
||||
task 默认会被自动添加到 `TaskCenter.default` 上,你可以使用 tag 配合 taskCenter 来组织 tasks:
|
||||
|
||||
```swift
|
||||
let plan = Plan.every(1.day)
|
||||
let task0 = plan.do(queue: myTaskQueue) { }
|
||||
let task1 = plan.do(queue: myTaskQueue) { }
|
||||
|
||||
TaskCenter.default.addTags(["database", "log"], to: task1)
|
||||
TaskCenter.default.removeTag("log", from: task1)
|
||||
|
||||
TaskCenter.default.suspend(byTag: "log")
|
||||
TaskCenter.default.resume(byTag: "log")
|
||||
TaskCenter.default.cancel(byTag: "log")
|
||||
|
||||
TaskCenter.default.removeAll()
|
||||
|
||||
let myCenter = TaskCenter()
|
||||
myCenter.add(task0)
|
||||
```
|
||||
|
||||
### Suspend、Resume、Cancel
|
||||
|
||||
你可以 suspend,resume,cancel 一个 task。
|
||||
|
||||
|
@ -182,19 +203,22 @@ let task = Plan.every(1.minute).do { }
|
|||
// 会增加 task 的暂停计数
|
||||
task.suspend()
|
||||
|
||||
// 会减少 task 的暂停计数,不过不用担心过度减少,
|
||||
// 我会帮你处理好这些~
|
||||
task.suspensions == 1
|
||||
|
||||
// 会减少 task 的暂停计数
|
||||
// 不过不用担心过度减少,我会帮你处理好这些~
|
||||
task.resume()
|
||||
|
||||
// 取消任务,这会把任务从内部持有者那儿移除
|
||||
// 也就是说,会减少 task 的引用计数
|
||||
// 如果没有其它持有者的话,这个任务就会被释放
|
||||
task.suspensions == 0
|
||||
|
||||
// 会清零 task 的暂停计数
|
||||
// 被 cancel 的 task 即使重新设置其它调度规则也不会有任何作用了
|
||||
task.cancel()
|
||||
```
|
||||
|
||||
#### 子动作
|
||||
|
||||
你可以添加更多的 action 到一个 task 上去,并在任意时刻移除它们:
|
||||
你可以添加更多的 action 到 task,并在任意时刻移除它们。
|
||||
|
||||
```swift
|
||||
let dailyTask = Plan.every(1.day)
|
||||
|
@ -210,59 +234,6 @@ let key = dailyTask.addAction {
|
|||
dailyTask.removeAction(byKey: key)
|
||||
```
|
||||
|
||||
#### TaskCenter 和 Tag
|
||||
|
||||
Task 默认会被添加到 TaskCenter.default 上,你可以使用 tag 配合 taskCenter 来组织 tasks:
|
||||
|
||||
```swift
|
||||
let plan = Plan.every(1.day)
|
||||
let task0 = plan.do(queue: myTaskQueue) { }
|
||||
let task1 = plan.do(queue: myTaskQueue) { }
|
||||
|
||||
TaskCenter.default.addTags(["database", "log"], to: task1)
|
||||
TaskCenter.default.removeTag("log", from: task1)
|
||||
|
||||
TaskCenter.default.suspend(byTag: "log")
|
||||
TaskCenter.default.resume(byTag: "log")
|
||||
TaskCenter.default.cancel(byTag: "log")
|
||||
|
||||
TaskCenter.default.clear()
|
||||
|
||||
let myCenter = TaskCenter()
|
||||
myCenter.add(task0) // will remove task0 from default center.
|
||||
```
|
||||
|
||||
#### Timeline
|
||||
|
||||
你可以实时地观察 task 的当前时间线:
|
||||
|
||||
```swift
|
||||
let timeline = task.timeline
|
||||
print(timeline.initialization)
|
||||
print(timeline.firstExecution)
|
||||
print(timeline.lastExecution)
|
||||
print(timeline.estimatedNextExecution)
|
||||
```
|
||||
|
||||
#### Lifetime
|
||||
|
||||
也可以精确地设置 task 的寿命:
|
||||
|
||||
```swift
|
||||
// 会再 10 小时后取消该 task
|
||||
task.setLifetime(10.hours)
|
||||
|
||||
// 会给该 task 的寿命增加 1 小时
|
||||
task.addLifetime(1.hour)
|
||||
|
||||
task.restOfLifetime == 11.hours
|
||||
```
|
||||
|
||||
## 支持
|
||||
|
||||
- iOS 10.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+
|
||||
- Linux(Tested on Ubuntu 16.04)
|
||||
|
||||
## 安装
|
||||
|
||||
### CocoaPods
|
||||
|
@ -272,44 +243,45 @@ task.restOfLifetime == 11.hours
|
|||
use_frameworks!
|
||||
|
||||
target 'YOUR_TARGET_NAME' do
|
||||
pod 'Schedule', '~> 1.0'
|
||||
pod 'Schedule', '~> 2.0'
|
||||
end
|
||||
```
|
||||
|
||||
### Carthage
|
||||
|
||||
```ruby
|
||||
github "jianstm/Schedule" ~> 1.0
|
||||
```
|
||||
# Cartfile
|
||||
github "jianstm/Schedule" ~> 2.0
|
||||
```
|
||||
|
||||
### Swift Package Manager
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/jianstm/Schedule", .upToNextMajor("1.0.0"))
|
||||
.package(
|
||||
url: "https://github.com/jianstm/Schedule", .upToNextMajor(from: "2.0.0")
|
||||
)
|
||||
]
|
||||
```
|
||||
|
||||
## 致谢
|
||||
|
||||
项目灵感来自于 Dan Bader 的 [schedule](https://github.com/dbader/schedule)!
|
||||
|
||||
## 贡献
|
||||
|
||||
喜欢 **Schedule** 吗?谢谢!与此同时我需要你的帮助:
|
||||
喜欢 **Schedule** 吗?谢谢!!!
|
||||
|
||||
与此同时如果你想参与进来的话,你可以:
|
||||
|
||||
### 找 Bugs
|
||||
|
||||
Schedule 还是一个非常年轻的项目,很难说项目离 bug free 还有多远。如果你能帮 Schedule 找到或者解决还没被发现的 bug 的话,我将感激不尽!
|
||||
Schedule 还是一个非常年轻的项目,如果你能帮 Schedule 找到甚至解决潜在的 bugs 的话,那就太感谢啦!
|
||||
|
||||
### 新功能
|
||||
|
||||
对项目有什么新的想法吗?尽管在 issue 里分享出来,或者你也可以直接提交你的 Pull Request!
|
||||
有一些有趣的想法?尽管在 issue 里分享出来,或者直接提交你的 Pull Request!
|
||||
|
||||
### 改善文档
|
||||
|
||||
对 README 或者文档注释的改善建议在任何时候都非常欢迎,无论是错别字还是纠正我的蹩脚英文。对使用者来说,有时文档要比具体的代码实现要重要得多。
|
||||
任何时候都欢迎对 README 或者文档注释的修改建议,无论是错别字还是纠正我的蹩脚英文,🤣。
|
||||
|
||||
### 分享
|
||||
## 致谢
|
||||
|
||||
无疑,用的人越多,项目就会变得越健壮,所以,star!fork!然后告诉你的朋友们吧!
|
||||
项目灵感来自 Dan Bader 的 [schedule](https://github.com/dbader/schedule)!
|
|
@ -27,7 +27,6 @@
|
|||
629FAA752255D6B100ED5D67 /* MonthdayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 629FAA742255D6B100ED5D67 /* MonthdayTests.swift */; };
|
||||
OBJ_49 /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* Atomic.swift */; };
|
||||
OBJ_50 /* Bag.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* Bag.swift */; };
|
||||
OBJ_51 /* DeinitObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* DeinitObserver.swift */; };
|
||||
OBJ_53 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* Extensions.swift */; };
|
||||
OBJ_54 /* Interval.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* Interval.swift */; };
|
||||
OBJ_55 /* Monthday.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* Monthday.swift */; };
|
||||
|
@ -42,7 +41,6 @@
|
|||
OBJ_81 /* AtomicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_26 /* AtomicTests.swift */; };
|
||||
OBJ_82 /* BagTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_27 /* BagTests.swift */; };
|
||||
OBJ_83 /* TimeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_28 /* TimeTests.swift */; };
|
||||
OBJ_84 /* DeinitObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_29 /* DeinitObserverTests.swift */; };
|
||||
OBJ_85 /* ExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_30 /* ExtensionsTests.swift */; };
|
||||
OBJ_86 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_31 /* Helpers.swift */; };
|
||||
OBJ_87 /* PlanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_32 /* PlanTests.swift */; };
|
||||
|
@ -75,7 +73,6 @@
|
|||
629FAA722255D1CA00ED5D67 /* PeriodTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeriodTests.swift; sourceTree = "<group>"; };
|
||||
629FAA742255D6B100ED5D67 /* MonthdayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthdayTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_10 /* Bag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bag.swift; sourceTree = "<group>"; };
|
||||
OBJ_11 /* DeinitObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeinitObserver.swift; sourceTree = "<group>"; };
|
||||
OBJ_13 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
|
||||
OBJ_14 /* Interval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Interval.swift; sourceTree = "<group>"; };
|
||||
OBJ_15 /* Monthday.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Monthday.swift; sourceTree = "<group>"; };
|
||||
|
@ -89,7 +86,6 @@
|
|||
OBJ_26 /* AtomicTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtomicTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_27 /* BagTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BagTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_28 /* TimeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_29 /* DeinitObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeinitObserverTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_30 /* ExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionsTests.swift; sourceTree = "<group>"; };
|
||||
OBJ_31 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
|
||||
OBJ_32 /* PlanTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlanTests.swift; sourceTree = "<group>"; };
|
||||
|
@ -139,7 +135,6 @@
|
|||
children = (
|
||||
OBJ_26 /* AtomicTests.swift */,
|
||||
OBJ_27 /* BagTests.swift */,
|
||||
OBJ_29 /* DeinitObserverTests.swift */,
|
||||
OBJ_30 /* ExtensionsTests.swift */,
|
||||
OBJ_31 /* Helpers.swift */,
|
||||
629FAA6E2255CC2700ED5D67 /* IntervalTests.swift */,
|
||||
|
@ -193,7 +188,6 @@
|
|||
children = (
|
||||
OBJ_9 /* Atomic.swift */,
|
||||
OBJ_10 /* Bag.swift */,
|
||||
OBJ_11 /* DeinitObserver.swift */,
|
||||
OBJ_13 /* Extensions.swift */,
|
||||
OBJ_14 /* Interval.swift */,
|
||||
OBJ_15 /* Monthday.swift */,
|
||||
|
@ -296,7 +290,6 @@
|
|||
files = (
|
||||
OBJ_49 /* Atomic.swift in Sources */,
|
||||
OBJ_50 /* Bag.swift in Sources */,
|
||||
OBJ_51 /* DeinitObserver.swift in Sources */,
|
||||
OBJ_53 /* Extensions.swift in Sources */,
|
||||
OBJ_54 /* Interval.swift in Sources */,
|
||||
OBJ_55 /* Monthday.swift in Sources */,
|
||||
|
@ -325,7 +318,6 @@
|
|||
OBJ_81 /* AtomicTests.swift in Sources */,
|
||||
OBJ_82 /* BagTests.swift in Sources */,
|
||||
OBJ_83 /* TimeTests.swift in Sources */,
|
||||
OBJ_84 /* DeinitObserverTests.swift in Sources */,
|
||||
OBJ_85 /* ExtensionsTests.swift in Sources */,
|
||||
OBJ_86 /* Helpers.swift in Sources */,
|
||||
629FAA712255D14800ED5D67 /* WeekdayTests.swift in Sources */,
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
import Foundation
|
||||
|
||||
#if canImport(ObjectiveC)
|
||||
|
||||
/// An observer that receives deinit event of the object.
|
||||
///
|
||||
/// let observer = DeinitObserver.observe(target) {
|
||||
/// print("\(target) deinit")
|
||||
/// }
|
||||
///
|
||||
/// observer.cancel()
|
||||
class DeinitObserver {
|
||||
|
||||
private var associateKey: Void = ()
|
||||
|
||||
private(set) weak var observed: AnyObject?
|
||||
|
||||
private var block: (() -> Void)?
|
||||
|
||||
private init(_ block: @escaping () -> Void) {
|
||||
self.block = block
|
||||
}
|
||||
|
||||
/// Observe deinit event of the object.
|
||||
@discardableResult
|
||||
static func observe(
|
||||
_ object: AnyObject,
|
||||
using block: @escaping () -> Void
|
||||
) -> DeinitObserver {
|
||||
let observer = DeinitObserver(block)
|
||||
observer.observed = object
|
||||
|
||||
objc_setAssociatedObject(object, &observer.associateKey, observer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
|
||||
return observer
|
||||
}
|
||||
|
||||
/// Cancel observing.
|
||||
func cancel() {
|
||||
block = nil
|
||||
if let o = observed {
|
||||
objc_setAssociatedObject(o, &associateKey, nil, .OBJC_ASSOCIATION_ASSIGN)
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
block?()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -20,27 +20,27 @@ public struct Plan: Sequence {
|
|||
/// Schedules a task with this plan.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - queue: The dispatch queue to which the block should be dispatched.
|
||||
/// - block: A block to be executed when time is up.
|
||||
/// - queue: The dispatch queue to which the action should be dispatched.
|
||||
/// - action: A block to be executed when time is up.
|
||||
/// - Returns: The task just created.
|
||||
public func `do`(
|
||||
queue: DispatchQueue,
|
||||
block: @escaping (Task) -> Void
|
||||
action: @escaping (Task) -> Void
|
||||
) -> Task {
|
||||
return Task(plan: self, queue: queue, block: block)
|
||||
return Task(plan: self, queue: queue, action: action)
|
||||
}
|
||||
|
||||
/// Schedules a task with this plan.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - queue: The dispatch queue to which the block should be dispatched.
|
||||
/// - block: A block to be executed when time is up.
|
||||
/// - queue: The dispatch queue to which the action should be dispatched.
|
||||
/// - action: A block to be executed when time is up.
|
||||
/// - Returns: The task just created.
|
||||
public func `do`(
|
||||
queue: DispatchQueue,
|
||||
block: @escaping () -> Void
|
||||
action: @escaping () -> Void
|
||||
) -> Task {
|
||||
return self.do(queue: queue, block: { (_) in block() })
|
||||
return self.do(queue: queue, action: { (_) in action() })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,14 +12,14 @@ extension Plan {
|
|||
/// `do(queue: _, onElapse: _)`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - mode: The mode to which the block should be added.
|
||||
/// - block: A block to be executed when time is up.
|
||||
/// - mode: The mode to which the action should be added.
|
||||
/// - action: A block to be executed when time is up.
|
||||
/// - Returns: The task just created.
|
||||
public func `do`(
|
||||
mode: RunLoop.Mode = .common,
|
||||
block: @escaping (Task) -> Void
|
||||
action: @escaping (Task) -> Void
|
||||
) -> Task {
|
||||
return RunLoopTask(plan: self, mode: mode, block: block)
|
||||
return RunLoopTask(plan: self, mode: mode, action: action)
|
||||
}
|
||||
|
||||
/// Schedules a task with this plan.
|
||||
|
@ -32,15 +32,15 @@ extension Plan {
|
|||
/// `do(queue: _, onElapse: _)`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - mode: The mode to which the block should be added.
|
||||
/// - block: A block to be executed when time is up.
|
||||
/// - mode: The mode to which the action should be added.
|
||||
/// - action: A block to be executed when time is up.
|
||||
/// - Returns: The task just created.
|
||||
public func `do`(
|
||||
mode: RunLoop.Mode = .common,
|
||||
block: @escaping () -> Void
|
||||
action: @escaping () -> Void
|
||||
) -> Task {
|
||||
return self.do(mode: mode) { _ in
|
||||
block()
|
||||
action()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ private final class RunLoopTask: Task {
|
|||
init(
|
||||
plan: Plan,
|
||||
mode: RunLoop.Mode,
|
||||
block: @escaping (Task) -> Void
|
||||
action: @escaping (Task) -> Void
|
||||
) {
|
||||
super.init(plan: plan, queue: nil) { (task) in
|
||||
guard let task = task as? RunLoopTask, let timer = task.timer else { return }
|
||||
|
@ -65,7 +65,7 @@ private final class RunLoopTask: Task {
|
|||
repeats: false
|
||||
) { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
block(self)
|
||||
action(self)
|
||||
}
|
||||
|
||||
RunLoop.current.add(timer, forMode: mode)
|
||||
|
|
|
@ -16,36 +16,49 @@ extension BagKey {
|
|||
/// `Task` represents a timing task.
|
||||
open class Task {
|
||||
|
||||
/// The unique id of this task.
|
||||
public let id = UUID()
|
||||
|
||||
public typealias Action = (Task) -> Void
|
||||
// MARK: - Private properties
|
||||
|
||||
private let _lock = NSRecursiveLock()
|
||||
|
||||
private var _iterator: AnyIterator<Interval>
|
||||
private var _timer: DispatchSourceTimer
|
||||
private let _timer: DispatchSourceTimer
|
||||
|
||||
private lazy var _actions = Bag<Action>()
|
||||
|
||||
private lazy var _suspensionCount: UInt64 = 0
|
||||
private lazy var _suspensionCount: Int = 0
|
||||
private lazy var _executionCount: Int = 0
|
||||
|
||||
private var _firstExecutionDate: Date?
|
||||
private var _lastExecutionDate: Date?
|
||||
private var _estimatedNextExecutionDate: Date?
|
||||
private lazy var _executionDates: [Date]? = nil
|
||||
private lazy var _estimatedNextExecutionDate: Date? = nil
|
||||
|
||||
private weak var _taskCenter: TaskCenter?
|
||||
private let _taskCenterLock = NSRecursiveLock()
|
||||
|
||||
private var associateKey = 1
|
||||
|
||||
// MARK: - Public properties
|
||||
|
||||
/// The unique id of this task.
|
||||
public let id = UUID()
|
||||
|
||||
public typealias Action = (Task) -> Void
|
||||
|
||||
/// The date of creation.
|
||||
public let creationDate = Date()
|
||||
|
||||
/// The date of first execution.
|
||||
open var firstExecutionDate: Date? {
|
||||
return _lock.withLock { _firstExecutionDate }
|
||||
return _lock.withLock { _executionDates?.first }
|
||||
}
|
||||
|
||||
/// The date of last execution.
|
||||
open var lastExecutionDate: Date? {
|
||||
return _lock.withLock { _lastExecutionDate }
|
||||
return _lock.withLock { _executionDates?.last }
|
||||
}
|
||||
|
||||
/// Histories of executions.
|
||||
open var executionDates: [Date]? {
|
||||
return _lock.withLock { _executionDates }
|
||||
}
|
||||
|
||||
/// The date of estimated next execution.
|
||||
|
@ -53,14 +66,40 @@ open class Task {
|
|||
return _lock.withLock { _estimatedNextExecutionDate }
|
||||
}
|
||||
|
||||
private weak var _taskCenter: TaskCenter?
|
||||
/// The number of task executions.
|
||||
public var executionCount: Int {
|
||||
return _lock.withLock {
|
||||
_executionCount
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of task suspensions.
|
||||
public var suspensionCount: Int {
|
||||
return _lock.withLock {
|
||||
_suspensionCount
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of actions in this task.
|
||||
public var actionCount: Int {
|
||||
return _lock.withLock {
|
||||
_actions.count
|
||||
}
|
||||
}
|
||||
|
||||
/// A Boolean indicating whether the task was canceled.
|
||||
public var isCancelled: Bool {
|
||||
return _lock.withLock {
|
||||
_timer.isCancelled
|
||||
}
|
||||
}
|
||||
|
||||
/// The task center to which this task currently belongs.
|
||||
open var taskCenter: TaskCenter? {
|
||||
return _lock.withLock { _taskCenter }
|
||||
}
|
||||
|
||||
private let _taskCenterLock = NSRecursiveLock()
|
||||
// MARK: - Task center
|
||||
|
||||
/// Adds this task to the given task center.
|
||||
func addToTaskCenter(_ center: TaskCenter) {
|
||||
|
@ -86,21 +125,23 @@ open class Task {
|
|||
center.remove(self)
|
||||
}
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
/// Initializes a timing task.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - plan: The plan.
|
||||
/// - queue: The dispatch queue to which the block should be dispatched.
|
||||
/// - block: A block to be executed when time is up.
|
||||
/// - queue: The dispatch queue to which the action should be dispatched.
|
||||
/// - action: A block to be executed when time is up.
|
||||
init(
|
||||
plan: Plan,
|
||||
queue: DispatchQueue?,
|
||||
block: @escaping (Task) -> Void
|
||||
action: @escaping (Task) -> Void
|
||||
) {
|
||||
_iterator = plan.makeIterator()
|
||||
_timer = DispatchSource.makeTimerSource(queue: queue)
|
||||
|
||||
_actions.append(block)
|
||||
_actions.append(action)
|
||||
|
||||
_timer.setEventHandler { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
@ -118,19 +159,18 @@ open class Task {
|
|||
}
|
||||
|
||||
deinit {
|
||||
print("deinit")
|
||||
while _suspensionCount > 0 {
|
||||
_timer.resume()
|
||||
_suspensionCount -= 1
|
||||
}
|
||||
|
||||
cancel()
|
||||
|
||||
taskCenter?.remove(self)
|
||||
}
|
||||
|
||||
private func elapse() {
|
||||
scheduleNextExecution()
|
||||
execute()
|
||||
executeNow()
|
||||
}
|
||||
|
||||
private func scheduleNextExecution() {
|
||||
|
@ -151,60 +191,37 @@ open class Task {
|
|||
}
|
||||
|
||||
/// Execute this task now, without interrupting its plan.
|
||||
open func execute() {
|
||||
open func executeNow() {
|
||||
let actions = _lock.withLock { () -> Bag<Task.Action> in
|
||||
let now = Date()
|
||||
if _firstExecutionDate == nil {
|
||||
_firstExecutionDate = now
|
||||
if _executionDates == nil {
|
||||
_executionDates = [now]
|
||||
} else {
|
||||
_executionDates?.append(now)
|
||||
}
|
||||
_lastExecutionDate = now
|
||||
_executionCount += 1
|
||||
return _actions
|
||||
}
|
||||
actions.forEach { $0(self) }
|
||||
}
|
||||
|
||||
/// Host this task to an object, that is, when the object deallocates, this task will be cancelled.
|
||||
#if canImport(ObjectiveC)
|
||||
open func host(to target: AnyObject) {
|
||||
DeinitObserver.observe(target) { [weak self] in
|
||||
self?.cancel()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// The number of task executions.
|
||||
public var executionCount: Int {
|
||||
return _lock.withLock {
|
||||
_executionCount
|
||||
}
|
||||
}
|
||||
|
||||
/// A Boolean indicating whether the task was canceled.
|
||||
public var isCancelled: Bool {
|
||||
return _lock.withLock {
|
||||
_timer.isCancelled
|
||||
}
|
||||
}
|
||||
// MARK: - Features
|
||||
|
||||
/// Reschedules this task with the new plan.
|
||||
public func reschedule(_ new: Plan) {
|
||||
_lock.withLockVoid {
|
||||
if _timer.isCancelled { return }
|
||||
|
||||
_iterator = new.makeIterator()
|
||||
}
|
||||
scheduleNextExecution()
|
||||
}
|
||||
|
||||
/// The number of task suspensions.
|
||||
public var suspensionCount: UInt64 {
|
||||
return _lock.withLock {
|
||||
_suspensionCount
|
||||
}
|
||||
}
|
||||
|
||||
/// Suspends this task.
|
||||
public func suspend() {
|
||||
_lock.withLockVoid {
|
||||
if _timer.isCancelled { return }
|
||||
|
||||
if _suspensionCount < UInt64.max {
|
||||
_timer.suspend()
|
||||
_suspensionCount += 1
|
||||
|
@ -215,6 +232,8 @@ open class Task {
|
|||
/// Resumes this task.
|
||||
public func resume() {
|
||||
_lock.withLockVoid {
|
||||
if _timer.isCancelled { return }
|
||||
|
||||
if _suspensionCount > 0 {
|
||||
_timer.resume()
|
||||
_suspensionCount -= 1
|
||||
|
@ -226,16 +245,7 @@ open class Task {
|
|||
public func cancel() {
|
||||
_lock.withLockVoid {
|
||||
_timer.cancel()
|
||||
}
|
||||
TaskCenter.default.remove(self)
|
||||
}
|
||||
|
||||
// MARK: - Action
|
||||
|
||||
/// The number of actions in this task.
|
||||
public var countOfActions: Int {
|
||||
return _lock.withLock {
|
||||
_actions.count
|
||||
_suspensionCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
import XCTest
|
||||
@testable import Schedule
|
||||
|
||||
#if canImport(ObjectiveC)
|
||||
|
||||
final class DeinitObserverTests: XCTestCase {
|
||||
|
||||
func testObserve() {
|
||||
var i = 0
|
||||
let fn = {
|
||||
let obj = NSObject()
|
||||
DeinitObserver.observe(obj) {
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
fn()
|
||||
XCTAssertEqual(i, 1)
|
||||
}
|
||||
|
||||
func testCancel() {
|
||||
var i = 0
|
||||
let fn = {
|
||||
let obj = NSObject()
|
||||
let observer = DeinitObserver.observe(obj) {
|
||||
i += 1
|
||||
}
|
||||
observer.cancel()
|
||||
}
|
||||
fn()
|
||||
XCTAssertEqual(i, 0)
|
||||
}
|
||||
|
||||
static var allTests = [
|
||||
("testObserve", testObserve),
|
||||
("testCancel", testCancel)
|
||||
]
|
||||
}
|
||||
|
||||
#endif
|
|
@ -21,6 +21,8 @@ final class TaskTests: XCTestCase {
|
|||
|
||||
XCTAssertNotNil(task.lastExecutionDate)
|
||||
XCTAssertTrue(task.lastExecutionDate!.interval(since: date).isAlmostEqual(to: 0.01.second, leeway: leeway))
|
||||
|
||||
XCTAssertEqual(task.executionDates!.count, 1)
|
||||
}
|
||||
|
||||
func testAfter() {
|
||||
|
@ -90,6 +92,33 @@ final class TaskTests: XCTestCase {
|
|||
waitForExpectations(timeout: 0.1)
|
||||
}
|
||||
|
||||
func testExecuteNow() {
|
||||
let e = expectation(description: "testExecuteNow")
|
||||
let task = Plan.never.do {
|
||||
e.fulfill()
|
||||
}
|
||||
task.executeNow()
|
||||
waitForExpectations(timeout: 0.1)
|
||||
}
|
||||
|
||||
func testReschedule() {
|
||||
let e = expectation(description: "testReschedule")
|
||||
var i = 0
|
||||
let task = Plan.after(0.01.second).do(queue: .global()) { (task) in
|
||||
i += 1
|
||||
if task.executionCount == 3 && task.estimatedNextExecutionDate == nil {
|
||||
e.fulfill()
|
||||
}
|
||||
if task.executionCount > 3 {
|
||||
XCTFail("should never come here")
|
||||
}
|
||||
}
|
||||
DispatchQueue.global().async(after: 0.02.second) {
|
||||
task.reschedule(Plan.every(0.01.second).first(2))
|
||||
}
|
||||
waitForExpectations(timeout: 0.1)
|
||||
}
|
||||
|
||||
func testSuspendResume() {
|
||||
let task = Plan.never.do { }
|
||||
XCTAssertEqual(task.suspensionCount, 0)
|
||||
|
@ -108,49 +137,6 @@ final class TaskTests: XCTestCase {
|
|||
XCTAssertTrue(task.isCancelled)
|
||||
}
|
||||
|
||||
func testExecuteNow() {
|
||||
let e = expectation(description: "testExecuteNow")
|
||||
let task = Plan.never.do {
|
||||
e.fulfill()
|
||||
}
|
||||
task.execute()
|
||||
waitForExpectations(timeout: 0.1)
|
||||
}
|
||||
|
||||
func testHost() {
|
||||
let e = expectation(description: "testHost")
|
||||
let fn = {
|
||||
let obj = NSObject()
|
||||
let task = Plan.after(0.1.second).do(queue: .main, block: {
|
||||
XCTFail("should never come here")
|
||||
})
|
||||
task.host(to: obj)
|
||||
}
|
||||
fn()
|
||||
DispatchQueue.main.async(after: 0.2.seconds) {
|
||||
e.fulfill()
|
||||
}
|
||||
waitForExpectations(timeout: 1)
|
||||
}
|
||||
|
||||
func testReschedule() {
|
||||
let e = expectation(description: "testReschedule")
|
||||
var i = 0
|
||||
let task = Plan.after(0.01.second).do(queue: .global()) { (task) in
|
||||
i += 1
|
||||
if task.executionCount == 3 && task.estimatedNextExecutionDate == nil {
|
||||
e.fulfill()
|
||||
}
|
||||
if task.executionCount > 3 {
|
||||
XCTFail("should never come here")
|
||||
}
|
||||
}
|
||||
DispatchQueue.global().async(after: 0.02.second) {
|
||||
task.reschedule(Plan.every(0.01.second).first(2))
|
||||
}
|
||||
waitForExpectations(timeout: 1)
|
||||
}
|
||||
|
||||
func testAddAndRemoveActions() {
|
||||
let e = expectation(description: "testAddAndRemoveActions")
|
||||
let task = Plan.after(0.1.second).do { }
|
||||
|
@ -159,16 +145,16 @@ final class TaskTests: XCTestCase {
|
|||
XCTAssertTrue(Date().timeIntervalSince(date).isAlmostEqual(to: 0.1, leeway: 0.1))
|
||||
e.fulfill()
|
||||
}
|
||||
XCTAssertEqual(task.countOfActions, 2)
|
||||
XCTAssertEqual(task.actionCount, 2)
|
||||
waitForExpectations(timeout: 0.5)
|
||||
|
||||
task.removeAction(byKey: key)
|
||||
XCTAssertEqual(task.countOfActions, 1)
|
||||
XCTAssertEqual(task.actionCount, 1)
|
||||
|
||||
task.cancel()
|
||||
|
||||
task.removeAllActions()
|
||||
XCTAssertEqual(task.countOfActions, 0)
|
||||
XCTAssertEqual(task.actionCount, 0)
|
||||
}
|
||||
|
||||
static var allTests = [
|
||||
|
@ -177,11 +163,10 @@ final class TaskTests: XCTestCase {
|
|||
("testTaskCenter", testTaskCenter),
|
||||
("testDispatchQueue", testDispatchQueue),
|
||||
("testThread", testThread),
|
||||
("testExecuteNow", testExecuteNow),
|
||||
("testReschedule", testReschedule),
|
||||
("testSuspendResume", testSuspendResume),
|
||||
("testCancel", testCancel),
|
||||
("testExecuteNow", testExecuteNow),
|
||||
("testHost", testHost),
|
||||
("testReschedule", testReschedule),
|
||||
("testAddAndRemoveActions", testAddAndRemoveActions)
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue