Skip to content

Instantly share code, notes, and snippets.

@julasamer
Last active August 29, 2015 14:05
Show Gist options
  • Save julasamer/215bc07e6074e8770c8f to your computer and use it in GitHub Desktop.
Save julasamer/215bc07e6074e8770c8f to your computer and use it in GitHub Desktop.
Timer.swift - A Timer class
/**
Copyright (c) 2014 Julian Asamer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import Foundation
/**
This class offers several class methods for timer-related tasks.
All of the following examples take a dispatch queue as an optional parameter to run the action on.
Examples:
Execute an action after a fixed delay:
Timer.delay(1, action: { println("Hello 1 second later!") })
Execute an action in a fixed interval and firing prematurely / stopping the timer:
let timer = Timer.repeat(interval: 0.5) { (timer, executionCount) -> () in
println("Hello, this is execution #\(executionCount + 1)")
}
//...
timer.fire()
//...
timer.stop()
Execute an action in fixed intervals for a certain number of times:
Timer.repeat(interval: 0.5, maximumExecutionCount: 5) { (timer, executionCount) -> () in
println("Hello, this is execution #\(executionCount + 1)")
}
Execute an action an arbitrary number of times, and stop at some point:
Timer.repeat(interval: 0.5) { (timer, executionCount) -> () in
println("Hello, this is execution #\(executionCount + 1)")
if someCondition {
println("Stopping now.");
timer.stop()
}
}
Execute an action with growing delays. For example, when requesting data from a server that doesn't respond, it makes sense to have growing delays between each attempt. If it's your own server, it might save you from DDOSing yourself. It also limits the computation power wasted on the device.
In this example, the action is executed with delays of 0.2, 0.4, 0.8, 1.6, ... seconds, but with a maximum of 10 seconds.
Timer.repeatWithBackoff(initialDelay: 0.2,
backOffFactor: 2.0,
maximumDelay: 10.0,
action: { (timer, executionCount) -> () in
println("This action is executed in growing delays.")
}
)
Execute an action with user-specified delays (you figure out something to do with that).
let digitsOfΠ: [Timer.TimeInterval] = [3,1,4,1,5,9,2,6,5,3]
Timer.repeat(
delayBlock: { (executionCount) -> Timer.TimeInterval in
return digitsOfΠ[executionCount % 10]
},
action: { (timer, executionCount) -> () in
println("What is this!")
}
)
*/
public class Timer {
/**
Represents a time interval in seconds
*/
public typealias TimeInterval = Double
/**
An action executed by a Timer if it is repeated. A Timer object is passed to allow stopping of the timer, as well as the number of executions.
*/
public typealias TimerAction = (timer: Timer, executionCount: Int) -> ()
/**
An function that returns a time interval.
*/
public typealias DelayBlock = (executionCount: Int) -> TimeInterval
/**
Executes a block after a given delay.
:param: delay The delay in seconds after which the action block is executed
:param: dispatchQueue Optional.
A dispatch queue on which to execute the action.
The main dispatch queue is used by default.
:param: action The action to execute.
*/
public class func delay(
delay: TimeInterval,
dispatchQueue: dispatch_queue_t = dispatch_get_main_queue(),
action: () -> ()) {
Timer(
delayBlock: { _ in delay },
dispatchQueue: dispatchQueue,
maximumExecutions: 1,
action: {
_, _ in
action()
}
)
}
/**
Executes a block in regular intervals.
:param: interval The interval in seconds in which the action will be executed.
:param: dispatchQueue Optional.
A dispatch queue on which to execute the action.
The main dispatch queue is used by default.
:param: maximumExecutionCount Optional.
The maximum number of times the action will be executed.
Pass nil to not have a limit.
Nil by default.
:param: action The action to execute.
*/
public class func repeat(
#interval: TimeInterval,
dispatchQueue: dispatch_queue_t = dispatch_get_main_queue(),
maximumExecutionCount: Int? = nil,
action: TimerAction) -> Timer {
return Timer(
delayBlock: { _ in interval },
dispatchQueue: dispatchQueue,
maximumExecutions: maximumExecutionCount,
action: action
)
}
/**
Executes a block in growing intervals. For example, with an initialDelay of 1 and a backOffFactor of 2,
the action will be executed using the following intervals: 1, 2, 4, 8, 16, ...
:param: initialDelay The delay in seconds after which the action will be executed the first time.
:param: backOffFactor Optional.
The delay will be increased by this factor with each execution of the action.
1.5 by default.
:param: maximumDelay Optional.
The delay will not increase past this maximum delay (also passed in seconds).
Pass nil to not use a maximum delay.
Nil by default.
:param: dispatchQueue Optional.
A dispatch queue on which to execute the action.
The main dispatch queue is used by default.
:param: maximumExecutionCount Optional.
The maximum number of times the action will be executed. Pass nil to not have a limit.
Nil by default.
:param: action The action to execute.
*/
public class func repeatWithBackoff (
#initialDelay: TimeInterval,
backOffFactor: Double = 1.5,
maximumDelay: TimeInterval? = nil,
dispatchQueue: dispatch_queue_t = dispatch_get_main_queue(),
maximumExecutionCount: Int? = nil,
action: TimerAction) -> Timer {
return Timer(
delayBlock: {
executionCount in
let timeInterval = TimeInterval(initialDelay * pow(backOffFactor, Double(executionCount)))
if let maximum = maximumDelay { return min(timeInterval, maximum) }
return timeInterval
},
dispatchQueue: dispatchQueue,
maximumExecutions: maximumExecutionCount,
action: action
)
}
/**
Executes a block with user-specified delays.
:param: delayBlock Should return the delay in seconds in which the next execution of the action should occur.
:param: dispatchQueue Optional.
A dispatch queue on which to execute the action.
The main dispatch queue is used by default.
:param: maximumExecutionCount Optional.
The maximum number of times the action will be executed.
Pass nil to not have a limit.
Nil by default.
:param: action The action to execute.
*/
public class func repeat (
#delayBlock: DelayBlock,
dispatchQueue: dispatch_queue_t = dispatch_get_main_queue(),
maximumExecutionCount: Int? = nil,
action: TimerAction) -> Timer {
return Timer(
delayBlock: delayBlock,
dispatchQueue: dispatchQueue,
maximumExecutions: maximumExecutionCount,
action: action
)
}
private let dispatchQueue: dispatch_queue_t
private let delayBlock: DelayBlock
private let action: TimerAction
private let maximumExecutions: Int?
private var currentExecutionCount: Int = 0
private var isDone: Bool = false
private var selfRetain: Timer?
private init(delayBlock: DelayBlock, dispatchQueue: dispatch_queue_t, maximumExecutions: Int?, action: TimerAction) {
self.delayBlock = delayBlock
self.dispatchQueue = dispatchQueue
self.maximumExecutions = maximumExecutions
self.action = action
self.selfRetain = self
self.startTimer()
}
/**
Stops the Timer. No actions will be executed anymore.
*/
public func stop() {
self.isDone = true
self.selfRetain = nil
}
/**
Fires the timer prematurely. The action is only executed if the Timer is still running.
*/
public func fire() {
if self.isDone { return }
self.action(timer: self, executionCount: self.currentExecutionCount)
self.currentExecutionCount++
if let maximum = self.maximumExecutions {
if self.currentExecutionCount >= self.maximumExecutions { self.isDone = true }
}
if !self.isDone {
self.startTimer()
} else {
self.selfRetain = nil
}
}
private func startTimer() {
let delay = Int64(self.delayBlock(executionCount: self.currentExecutionCount) * Double(NSEC_PER_SEC))
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, delay), self.dispatchQueue) {
[weak self] in
if let weakSelf = self { weakSelf.fire() }
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment