Skip to content

Instantly share code, notes, and snippets.

@jdriscoll
Created August 22, 2016 20:52
Show Gist options
  • Save jdriscoll/02a3b86a1dc52f62d1e4492d4b6498c9 to your computer and use it in GitHub Desktop.
Save jdriscoll/02a3b86a1dc52f62d1e4492d4b6498c9 to your computer and use it in GitHub Desktop.
5 minute interval clock
// Copyright (c) 2016 Justin Driscoll <justin@driscolldev.com>
//
// 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
/// Wraps a function to be executed on every clock 'tick'
public class Ticker {
let id: Int
let label: String
let f: (tick: Int) -> Void
init(id: Int, label: String, f: (tick: Int) -> Void) {
self.id = id
self.label = label
self.f = f
}
func stop() {
Clock.sharedInstance.removeTicker(self)
}
}
/// Singleton object that vends Tickers
public class Clock: NSObject {
// MARK: Public API
public static func ticker(label label: String?, _ f:(tick: Int) -> Void) -> Ticker {
return self.sharedInstance.addTicker((label == nil ? self.UNLABELED : label!), f: f)
}
public static func stop(label label: String) {
self.sharedInstance.removeLabel(label)
}
public static func stop(ticker ticker: Ticker) {
self.sharedInstance.removeTicker(ticker)
}
public static func pause() {
self.sharedInstance.pauseTimer()
}
public static func resume() {
self.sharedInstance.startTimer()
}
// MARK: Internal
private static let UNLABELED = "UNLABLED"
private static let sharedInstance = Clock()
private override init() { }
/// The default group label applied to Tickers
private var ticks = 0
private var nextId = 0
private var tickers: [String: [Int: Ticker]] = [:]
private var timer: NSTimer?
private func addTicker(label: String, f: (ticks: Int) -> Void) -> Ticker {
nextId += 1
let ticker = Ticker(id: nextId, label: label, f: f)
if tickers[label] == nil {
tickers[label] = [nextId: ticker]
}
else {
tickers[label]![nextId] = ticker
}
startTimer()
return ticker
}
private func removeTicker(ticker: Ticker) {
tickers[ticker.label]?.removeValueForKey(ticker.id)
if let label = tickers[ticker.label] where label.isEmpty {
removeLabel(ticker.label)
}
}
private func removeLabel(label: String) {
tickers.removeValueForKey(label)
if tickers.isEmpty {
pauseTimer()
}
}
private func pauseTimer() {
if let t = timer {
t.invalidate()
timer = nil
}
assert(timer == nil, "Ticker timer should be nil after being paused.")
}
private func startTimer() {
if tickers.isEmpty { return }
if timer == nil {
let roundedTimestamp = NSDate().roundedBackTo5MinuteInterval()
let nextFireDate = roundedTimestamp.dateByAddingTimeInterval(1 * 60)
let t = NSTimer(fireDate: nextFireDate, interval: 1 * 60, target: self, selector: #selector(tick), userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(t, forMode: NSRunLoopCommonModes)
timer = t
}
assert(timer != nil, "Ticker timer should not be nil after being started.")
}
func tick() {
tickers.values.flatMap { $0 }.forEach { $1.f(tick: self.ticks) }
self.ticks += 1
}
deinit {
timer?.invalidate()
}
}
private extension NSDate {
func roundedBackTo5MinuteInterval() -> NSDate {
let calendar = NSCalendar.currentCalendar()
var components = calendar.components([.Year, .Month, .Day, .Hour, .Minute, .Second], fromDate: self)
components.minute -= components.minute % 5
components.second = 0
return calendar.dateFromComponents(components)!
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment