Schedule timing task in Swift using a fluent API. (A friendly alternative to Timer)

Overview

Schedule(简体中文)

Schedule is a timing tasks scheduler written in Swift. It allows you run timing tasks with elegant and intuitive syntax.

Features

  • Elegant and intuitive API
  • Rich preset rules
  • Powerful management mechanism
  • Detailed execution history
  • Thread safe
  • Complete documentation
  • ~100%+ test coverage

Why You Should Use Schedule

Features Timer DispatchSourceTimer Schedule
Interval-based Schedule
📆  Date-based Schedule
🌈 Combined Plan Schedule
🗣️ Natural Language Parse
🏷 Batch Task Management
📝 Execution Record
🎡 Plan Reset
🚦 Suspend, Resume, Cancel
🍰 Child-action

Usage

Overview

Scheduling a task has never been so elegant and intuitive, all you have to do is:

// 1. define your plan:
let plan = Plan.after(3.seconds)

// 2. do your task:
let task = plan.do {
    print("3 seconds passed!")
}

Rules

Interval-based Schedule

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.

let t1 = Plan.every(1.second).do { }

let t2 = Plan.after(1.hour, repeating: 1.minute).do { }

let t3 = Plan.of(1.second, 2.minutes, 3.hours).do { }

Date-based Schedule

Configuring date-based Plan is the same, with the expressive Swift syntax, Schedule makes your code look like a fluent conversation.

let t1 = Plan.at(date).do { }

let t2 = Plan.every(.monday, .tuesday).at("9:00:00").do { }

let t3 = Plan.every(.september(30)).at(10, 30).do { }

let t4 = Plan.every("one month and ten days").do { }

let t5 = Plan.of(date0, date1, date2).do { }

Natural Language Parse

In addition, Schedule also supports simple natural language parsing.

let t1 = Plan.every("one hour and ten minutes").do { }

let t2 = Plan.every("1 hour, 5 minutes and 10 seconds").do { }

let t3 = Plan.every(.friday).at("9:00 pm").do { }

Period.registerQuantifier("many", for: 100 * 1000)
let t4 = Plan.every("many days").do { }

Combined Plan Schedule

Schedule provides several basic collection operators, which means you can use them to customize your own powerful plans.

/// Concat
let p0 = Plan.at(birthdate)
let p1 = Plan.every(1.year)
let birthday = p0.concat.p1
let t1 = birthday.do { 
    print("Happy birthday")
}

/// Merge
let p3 = Plan.every(.january(1)).at("8:00")
let p4 = Plan.every(.october(1)).at("9:00 AM")
let holiday = p3.merge(p4)
let t2 = holiday.do {
    print("Happy holiday")
}

/// First
let p5 = Plan.after(5.seconds).concat(Schedule.every(1.day))
let p6 = s5.first(10)

/// Until
let p7 = P.every(.monday).at(11, 12)
let p8 = p7.until(date)

Management

DispatchQueue

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.

Plan.every(1.second).do(queue: .global()) {
    print("On a globle queue")
}

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.

let task = Plan.every(1.second).do(mode: .default) {
    print("on default mode...")
}

Timeline

You can observe the execution record of the task in real time using the following properties.

task.creationDate

task.executionHistory

task.firstExecutionDate
task.lastExecutionDate

task.estimatedNextExecutionDate

TaskCenter & Tag

Tasks are automatically added to TaskCenter.default by default,you can organize them using tags and task center.

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)

Suspend,Resume, Cancel

You can suspend, resume, cancel a task.

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()

Action

You can add more actions to a task and remove them at any time you want:

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)

Installation

CocoaPods

# Podfile
use_frameworks!

target 'YOUR_TARGET_NAME' do
  pod 'Schedule', '~> 2.0'
end

Carthage

github "luoxiu/Schedule" ~> 2.0

Swift Package Manager

dependencies: [
    .package(
      url: "https://github.com/luoxiu/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!

Comments
  • Plan.every(5.second).do is not working

    Plan.every(5.second).do is not working

    Here is my simple code:

    task = Plan.every(5.second).do {
        self.checkIfUnauthorized()
    }
    task?.resume()
    

    This is only working one time. I am running this code on iOS 12.2 with Xcode 10.2.1.

    opened by olcayertas 9
  • Can't running

    Can't running

    
    import SwiftUI
    import Schedule
    
    struct TestView: View {
    	
    		var body: some View {
    		HStack {
    			
    			
    			Button(action:{
    				
    			
    				let plan = Plan.after(3.seconds)
    				let task = plan.do {
    					print("test 3")
    				}
    				print("created task")
    				//task.executeNow()
    			}){
    				
    				Text("test")
    			}
    
    	}
    }
    
    
    
    

    Hello, I am developing an app with SWIFTUI, but I don't know how to invoke the schedule. it output nothing for me. needs any setup for it?

    opened by a7e6j2 7
  • Proposal new logo to SCHEDULE

    Proposal new logo to SCHEDULE

    Hello sir, I'm a graphic designer, I'm interested in open source projects. I would like to design a logo for your Project. I will be happy to collaborate with you :)

    enhancement 
    opened by ggabogarcia 7
  • Allow usage of arrays in addition to variadic arguments in Interval and Plan functions

    Allow usage of arrays in addition to variadic arguments in Interval and Plan functions

    Although it is cleaner to use a variadic function when creating schedules (ex. via Plan.of(_ dates: Date...)), sometimes it is necessary to pass an array instead of a list of parameters, like when dealing with data obtained at runtime. This PR adds that ability. For example, the Plan.of(_:) function can now take an array of Dates like so:

    let dates = [Date(), Date(), Date(), Date(), Date()]
    Plan.of(dates).do { ... }
    

    The existing API has not changed whatsoever, the only difference being that the function implementations are now located in the array-variant of the function, and the existing variadic-variant (whose parameters are interpreted as an array in the function body) now simply passes its parameters into the array-variant.

    opened by WilsonGramer 6
  • daily task at a certain time...

    daily task at a certain time...

    first attempt:

    let everyWeekdayTask = Plan.every([.sunday, .monday, .tuesday, .wednesday, .thursday, .friday, .saturday]).at("12 pm").do {}
    

    result:

    (lldb) po everyWeekdayTask.estimatedNextExecutionDate
    nil
    

    second attempt:

            let sunPlan = Plan.every(.sunday).at("12 pm")
            let monPlan = Plan.every(.monday).at("12 pm")
            let tuePlan = Plan.every(.tuesday).at("12 pm")
            let wedPlan = Plan.every(.wednesday).at("12 pm")
            let thuPlan = Plan.every(.thursday).at("12 pm")
            let friPlan = Plan.every(.friday).at("12 pm")
            let satPlan = Plan.every(.saturday).at("12 pm")
            let everyWeekdayTask2 = sunPlan.concat(monPlan).concat(tuePlan).concat(wedPlan).concat(thuPlan).concat(friPlan).concat(satPlan).do {}
    

    result:

    (lldb) po everyWeekdayTask2.estimatedNextExecutionDate
    ▿ Optional<Date>
      ▿ some : 2019-10-20 09:00:00 +0000
        - timeIntervalSinceReferenceDate : 593254800.000214
    

    It seems to be working, but we lost one day. Today is Friday the 18th, and the nearest next timer operation is set to Sunday the 20th (do not look at the time, it is adjusted according to the timezone). That is, we lost somewhere Saturday.

    Can you tell me how to correctly make a task for each day at a certain time?

    opened by maxgribov 5
  • struct `Plan` api errors

    struct `Plan` api errors

    let t3 = Plan.every(.firday).at("9:00 pm").do { }
    

    error: Reference to member 'firday' cannot be resolved without a contextual type

    let t4 = Plan.every("tuesday").at("9:00 pm").do { }
    

    error: Static member 'at' cannot be used on instance of type 'Plan'

    the first option from the documentation... using: Xcode 11.1, swift 5

    opened by maxgribov 5
  • Memory Leak

    Memory Leak

    I noticed this library is leaking RunLoopTask:

    image

    and can be reproduced with this trivial macOS example:

    class ViewController: NSViewController {
        var task: Task?
        @IBAction func onButtonPress(_ sender: Any) {
            task = Plan.after(Interval(seconds: 2)).do {
                print("Hi")
            }
        }
    }
    
    opened by ApolloZhu 5
  • Does it still support iOS 10 ?

    Does it still support iOS 10 ?

    Hi @jianstm , I have built Schedule via Carthage (Xcode 9.4.1) then integrate to a project and they said: Module file's minimum deployment target is ios11.4 v11.4: /Users/hai/Frameworks/Schedule.framework/Modules/Schedule.swiftmodule/x86_64.swiftmodule

    I have checked Schedule's targets they are setting from 10.10, could you investigate this ?

    opened by haithngn 4
  • Task every day not work?

    Task every day not work?

    I am confused how it work? Assume that now is Friday 15:10:00

    let task = Plan.every(.friday).at(15,11).do { print("Execute task") }

    But nothing printed at 15:11 Is there something wrong here? (I tested in playground)

    opened by nhatquangz 3
  •  Add support for interval offsets

    Add support for interval offsets

    Adds the ability to offset a Task by a specific interval from its Plan, such so that the Task calculates its next run based on adding said interval to the Plan's next interval. This can be useful if one has a preexisting Plan and needs to uniformly adjust the times at which its Task runs (eg. timezone differences, etc.).

    Usage examples:

    plan.do(offsetBy: 3.hours) { ... }
    
    let calculateOffset = { () -> Interval? in
      // some computation to calculate offsets on the fly
    }
    plan.do(offsetBy: calculateOffset) { ... }
    
    opened by WilsonGramer 3
  • 修改Task的执行时间

    修改Task的执行时间

    你好,有个问题请教一下,这里有个两秒后执行的task,假设当前时间是t0 let somePlan = Plan.after(2.second) let after2Task = somePlan.do(onElapse: { //Do something }) 在after2Task即将开始执行前,假如是t0后1.9s,我调用了如下方法 after2Task.setLifetime(2.second) 录得after2Task实际执行的时间t1 预想t1-t0 = 3.9,但实际t1-t0 = 2。请问类似的效果应该如何实现

    opened by SciMagic 2
  • Is it still working ?

    Is it still working ?

    The code

    let planWorkTimer = Plan.every(.monday, .tuesday, .wednesday, .thursday, .friday).at("6:49 am").do {
                print("planWorkTimer is start")
    }
    

    not print.

    opened by stavigor 0
  • Suspension problem

    Suspension problem

    I have a task:

    let task = Plan.every(5.seconds).do(action: startSome)
    

    When I suspend() it somewhere and then resume for 10 seconds, I have 2 immediate calls of startSome. But what I want is call startSome in (5sec - time remaining when I called suspend). Is it possible?

    opened by vBoykoGit 0
  • Task is getting deallocated.

    Task is getting deallocated.

    I am using the task as shown below and used as a property to singleton class.

                schedulerTask = Plan.every(0.5).do(queue: .global()) {
    doSomething()
                }
    

    Which is resulting in this crash. Screenshot 2019-06-04 at 10 47 46 AM

    opened by kidsid-Ixigo 2
Releases(2.1.1)
  • 2.0.0(Apr 6, 2019)

    ❗️❗️❗️This release contains some breaking changes.

    Warning

    The initial design of Task refers to Timer: It will be implicitly held by an internal object, if you want to remove it, you need to explicitly call the invalidate/cancel method. But soon, I realized that it was easy to ignore this feature and caused memory leaks. So in 2.0.0, Task is no longer automatically held, that is, if no external variables are explicitly pointed to it, this task will be destroyed.

    Fixed

    • Calculation issue in every(_ weekday: Weekday) and every(_ monthday: Monthday).

    Added

    • TaskCenter. From now on, you can use your own task center to manage tasks.
    • task.executionDates. Records the date each time the task is executed.
    • More tests.

    Removed

    • task.timeline. All timeline properties are now accessible directly from the task.
    • plan.do(host: obj). Since tasks are no longer implicitly held by task centers, I don't think the host mechanism is necessary.

    Updated

    • Some renaming, to make the api more swift!
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0(Sep 26, 2018)

    • Rename struct Schedule to Plan It is not wise to let a type have the same name as framework.

    • Remove ParasiticTask Now, each constructor has the host parameter(default is nil).

    • Add RunLoopTask Before 1.x, Schedule will execute tasks on a global dispatch queue when time is up by default. Now tasks will be executed on the current thread, its implementation is based on RunLoop, which means that you need to ensure that the current thread has a runloop available. So it is still recommended to use dispatch queue to construct the task.

    Source code(tar.gz)
    Source code(zip)
Owner
Luo Xiu
iOS Developer, occasionally write web, server and cli apps.
Luo Xiu
Plugin-spell-timer - Spell Timer Plugin for Outlander

Spell Timer Plugin for Outlander This plugin provides variables for spells from

Outlander 0 Jan 22, 2022
SwiftyTimer allows you to instantly schedule delays and repeating timers using convenient closure syntax. It's time to get rid of Objective-C cruft.

SwiftyTimer Modern Swifty API for NSTimer SwiftyTimer allows you to instantly schedule delays and repeating timers using convenient closure syntax. It

Radek Pietruszewski 1.2k Dec 27, 2022
GCDTimer - Well tested Grand Central Dispatch (GCD) Timer in Swift

GCDTimer Well tested Grand Central Dispatch (GCD) Timer in Swift. Checkout the test file. Usage Long running timer import GCDTimer

Hemant Sapkota 183 Sep 9, 2022
Egg-Timer - Intermediate Swift Programming - Control Flow and Optionals

Egg-Timer Intermediate Swift Programming - Control Flow and Optionals What I lea

null 0 Jan 10, 2022
Egg Timer app helps you to cook your egg in the way you want

Egg Timer Egg Timer app helps you to cook your egg in the way you want. You need to decide on how do you want to eat your egg than just click the egg

Emrullah Cirit 2 Nov 29, 2022
THORChain vault churn countdown timer for macOS tray

ChurnCountdown Useful macOS tray app to show THORChain churn countdown. On start

null 0 Jan 5, 2022
OpenFocusTimer - Pomodoro timer with categories, reflection, history & statistics

OpenFocusTimer Pomodoro timer with categories, reflection, history & statistics.

Cihat Gündüz 18 Dec 13, 2022
Kommander is a Swift library to manage the task execution in different threads.

A lightweight, pure-Swift library for manage the task execution in different threads. Through the definition a simple but powerful concept, Kommand.

Intelygenz 173 Apr 11, 2022
Queues, timers, and task groups in Swift

Dispatcher eases the pain of using Grand Central Dispatch by introducing 4 new Swift classes. Dispatcher Queue Group Timer Requirements Swift 2.0+ Ins

Alec Larson 109 Jan 29, 2022
⚡️ Fast async task based Swift framework with focus on type safety, concurrency and multi threading

Our apps constantly do work. The faster you react to user input and produce an output, the more likely is that the user will continue to use your appl

Said Sikira 814 Oct 30, 2022
A Task Queue Class developed in Swift (by Marin Todorov)

TaskQueue Table of Contents Intro Installation CocoaPods Carthage Simple Examples Synchronous tasks Asynchronous tasks Serial and Concurrent Tasks GCD

Marin Todorov 677 Jan 3, 2023
Several synchronization primitives and task synchronization mechanisms introduced to aid in modern swift concurrency.

AsyncObjects Several synchronization primitives and task synchronization mechanisms introduced to aid in modern swift concurrency. Overview While Swif

SwiftyLab 20 Jan 3, 2023
AsyncTaskKit - contains some additions to async/await Task

AsyncTaskKit This repo contains some additions to async/await Task. In general i

Michał Zaborowski 0 Jan 2, 2022
Swift TableView pagination with async API request.

SwiftTableViewPagination Swift TableView pagination with async API request. Output UML Create puml file. $ cd SwiftTableViewPagination/scripts/swiftum

y-okudera 1 Dec 26, 2021
Slack message generator and API client, written in Swift with Result Builders and Concurrency

Slack Message Client This package provides a Swift object model for a Slack Block Kit message, as well as a Result Builder convenience interface for e

Mike Lewis 2 Jul 30, 2022
Automatically generate GraphQL queries and decode results into Swift objects, and also interact with arbitrary GitHub API endpoints

GitHub API and GraphQL Client This package provides a generic GitHub API client (GithubApiClient) as well as Codable-like GitHub GraphQL querying and

Mike Lewis 4 Aug 6, 2022
Throttle massive number of inputs in a single drop of one line API.

Icon credits: Lorc, Delapouite & contributors Throttler Throttler is a library that throttles unnecessarily repeated and massive inputs until the last

Jang Seoksoon 73 Nov 16, 2022
PixabayImageSearchSample - SwiftUI + LazyVGrid + Pixabay API + Codable + sync/async Sample

PixabayImageSearchSample SwiftUI + LazyVGrid + Pixabay API + Codable + sync/asyn

null 3 Nov 23, 2022
An introduction to using Swift's new concurrency features in SwiftUI

SwiftUI Concurrency Essentials An introduction to using Swift's new concurrency features in SwiftUI Discuss with me · Report Bug · Request Feature Art

Peter Friese 80 Dec 14, 2022