From 084ed4d812e556649dfb91f44543c606207a6597 Mon Sep 17 00:00:00 2001 From: Jean Azzopardi Date: Mon, 8 Aug 2016 11:37:50 +0200 Subject: [PATCH 1/2] Adds OneShotTimer and OneShotDispatch Timer Refactors ExecutionClosures to RepeatedExecutionClosure and OneShotExecutionClosure --- Chronos.xcodeproj/project.pbxproj | 50 +++++-- Chronos/DispatchTimer.swift | 8 +- Chronos/OneShotDispatchTimer.swift | 213 +++++++++++++++++++++++++++ Chronos/OneShotTimer.swift | 45 ++++++ Chronos/RepeatingTimer.swift | 11 +- Chronos/TimerInternal.swift | 9 +- Chronos/VariableTimer.swift | 8 +- ChronosTests/OneShotTimerTests.swift | 154 +++++++++++++++++++ 8 files changed, 473 insertions(+), 25 deletions(-) create mode 100644 Chronos/OneShotDispatchTimer.swift create mode 100644 Chronos/OneShotTimer.swift create mode 100644 ChronosTests/OneShotTimerTests.swift diff --git a/Chronos.xcodeproj/project.pbxproj b/Chronos.xcodeproj/project.pbxproj index 5bfd9b9..ee3e4fa 100644 --- a/Chronos.xcodeproj/project.pbxproj +++ b/Chronos.xcodeproj/project.pbxproj @@ -7,6 +7,21 @@ objects = { /* Begin PBXBuildFile section */ + 054C97901D5518F200C481BD /* OneShotDispatchTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C978F1D5518F200C481BD /* OneShotDispatchTimer.swift */; }; + 054C97911D551DBF00C481BD /* OneShotDispatchTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C978F1D5518F200C481BD /* OneShotDispatchTimer.swift */; }; + 054C97921D551DC000C481BD /* OneShotDispatchTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C978F1D5518F200C481BD /* OneShotDispatchTimer.swift */; }; + 054C97961D551FEA00C481BD /* OneShotTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C97951D551FEA00C481BD /* OneShotTimer.swift */; }; + 054C97971D55201F00C481BD /* OneShotTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C97951D551FEA00C481BD /* OneShotTimer.swift */; }; + 054C97981D55201F00C481BD /* OneShotTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C97951D551FEA00C481BD /* OneShotTimer.swift */; }; + 054C97991D55219000C481BD /* OneShotTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C97951D551FEA00C481BD /* OneShotTimer.swift */; }; + 054C979A1D55219100C481BD /* OneShotTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C97951D551FEA00C481BD /* OneShotTimer.swift */; }; + 054C979B1D55219200C481BD /* OneShotTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C97951D551FEA00C481BD /* OneShotTimer.swift */; }; + 054C979C1D5521E800C481BD /* OneShotTimerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C97931D551F2F00C481BD /* OneShotTimerTests.swift */; }; + 054C979D1D5521EE00C481BD /* OneShotTimerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C97931D551F2F00C481BD /* OneShotTimerTests.swift */; }; + 054C979E1D5521EF00C481BD /* OneShotTimerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C97931D551F2F00C481BD /* OneShotTimerTests.swift */; }; + 054C979F1D55220B00C481BD /* OneShotDispatchTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C978F1D5518F200C481BD /* OneShotDispatchTimer.swift */; }; + 054C97A01D55220C00C481BD /* OneShotDispatchTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C978F1D5518F200C481BD /* OneShotDispatchTimer.swift */; }; + 054C97A11D55220C00C481BD /* OneShotDispatchTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 054C978F1D5518F200C481BD /* OneShotDispatchTimer.swift */; }; 6422C4A61AD38BBC00D480E8 /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6422C4A51AD38BBC00D480E8 /* Timer.swift */; }; 6422C4A71AD38C6F00D480E8 /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6422C4A51AD38BBC00D480E8 /* Timer.swift */; }; 6422C4A81AD38C6F00D480E8 /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6422C4A51AD38BBC00D480E8 /* Timer.swift */; }; @@ -101,6 +116,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 054C978F1D5518F200C481BD /* OneShotDispatchTimer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneShotDispatchTimer.swift; sourceTree = ""; }; + 054C97931D551F2F00C481BD /* OneShotTimerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneShotTimerTests.swift; sourceTree = ""; }; + 054C97951D551FEA00C481BD /* OneShotTimer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneShotTimer.swift; sourceTree = ""; }; 6422C4A51AD38BBC00D480E8 /* Timer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Timer.swift; sourceTree = ""; }; 644FA4141ADE19560007B808 /* TimerInternal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimerInternal.swift; sourceTree = ""; }; 644FA4161ADE19760007B808 /* VariableTimer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VariableTimer.swift; sourceTree = ""; }; @@ -199,6 +217,8 @@ 64FFA21A1ADE1932006E36D3 /* RepeatingTimer.swift */, 64B3CE381ACDDA7E00145E36 /* DispatchTimer.swift */, 644FA4161ADE19760007B808 /* VariableTimer.swift */, + 054C97951D551FEA00C481BD /* OneShotTimer.swift */, + 054C978F1D5518F200C481BD /* OneShotDispatchTimer.swift */, 64B3CE1F1ACDDA2800145E36 /* Supporting Files */, ); path = Chronos; @@ -217,6 +237,7 @@ children = ( 644FA4221ADE19A50007B808 /* DispatchTimerTests.swift */, 64502B951ADE19C50008443B /* VariableTimerTests.swift */, + 054C97931D551F2F00C481BD /* OneShotTimerTests.swift */, 64B3CE2C1ACDDA2800145E36 /* Supporting Files */, ); path = ChronosTests; @@ -473,10 +494,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 054C97901D5518F200C481BD /* OneShotDispatchTimer.swift in Sources */, 644FA4151ADE19560007B808 /* TimerInternal.swift in Sources */, 64B3CE391ACDDA7E00145E36 /* DispatchTimer.swift in Sources */, 644FA4171ADE19760007B808 /* VariableTimer.swift in Sources */, 64FFA21B1ADE1932006E36D3 /* RepeatingTimer.swift in Sources */, + 054C97961D551FEA00C481BD /* OneShotTimer.swift in Sources */, 6422C4A61AD38BBC00D480E8 /* Timer.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -491,6 +514,9 @@ 644FA4201ADE19840007B808 /* RepeatingTimer.swift in Sources */, 644FA4191ADE197C0007B808 /* VariableTimer.swift in Sources */, 644FA41E1ADE19810007B808 /* TimerInternal.swift in Sources */, + 054C97991D55219000C481BD /* OneShotTimer.swift in Sources */, + 054C979C1D5521E800C481BD /* OneShotTimerTests.swift in Sources */, + 054C979F1D55220B00C481BD /* OneShotDispatchTimer.swift in Sources */, 64B8678C1ACFCE070020EDDA /* DispatchTimer.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -499,11 +525,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 054C97921D551DC000C481BD /* OneShotDispatchTimer.swift in Sources */, 64C884261C026886005DB548 /* TimerInternal.swift in Sources */, 64C884271C026886005DB548 /* Timer.swift in Sources */, 64C884281C026886005DB548 /* RepeatingTimer.swift in Sources */, 64C884291C026886005DB548 /* DispatchTimer.swift in Sources */, 64C8842A1C026886005DB548 /* VariableTimer.swift in Sources */, + 054C97981D55201F00C481BD /* OneShotTimer.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -517,6 +545,9 @@ 64C8842D1C0268A1005DB548 /* Timer.swift in Sources */, 64C8842E1C0268A1005DB548 /* RepeatingTimer.swift in Sources */, 64C8842F1C0268A1005DB548 /* DispatchTimer.swift in Sources */, + 054C979B1D55219200C481BD /* OneShotTimer.swift in Sources */, + 054C979E1D5521EF00C481BD /* OneShotTimerTests.swift in Sources */, + 054C97A11D55220C00C481BD /* OneShotDispatchTimer.swift in Sources */, 64C884301C0268A1005DB548 /* VariableTimer.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -525,11 +556,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 054C97911D551DBF00C481BD /* OneShotDispatchTimer.swift in Sources */, 644FA41B1ADE197F0007B808 /* TimerInternal.swift in Sources */, 648578B41AD0F49A005F4297 /* DispatchTimer.swift in Sources */, 644FA4181ADE197B0007B808 /* VariableTimer.swift in Sources */, 6422C4A71AD38C6F00D480E8 /* Timer.swift in Sources */, 644FA41F1ADE19840007B808 /* RepeatingTimer.swift in Sources */, + 054C97971D55201F00C481BD /* OneShotTimer.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -543,6 +576,9 @@ 644FA4211ADE19850007B808 /* RepeatingTimer.swift in Sources */, 644FA41A1ADE197C0007B808 /* VariableTimer.swift in Sources */, 644FA41C1ADE19800007B808 /* TimerInternal.swift in Sources */, + 054C979A1D55219100C481BD /* OneShotTimer.swift in Sources */, + 054C979D1D5521EE00C481BD /* OneShotTimerTests.swift in Sources */, + 054C97A01D55220C00C481BD /* OneShotDispatchTimer.swift in Sources */, 64B0D0F71AD0E58A0020EFE1 /* DispatchTimer.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -722,10 +758,7 @@ CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; DEFINES_MODULE = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", @@ -744,10 +777,7 @@ CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; CODE_SIGN_IDENTITY = "iPhone Developer"; DEFINES_MODULE = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = ChronosTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -888,7 +918,7 @@ DD9ED4691AD0B36A0068D45E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(DEVELOPER_FRAMEWORKS_DIR)", @@ -910,7 +940,7 @@ DD9ED46A1AD0B36A0068D45E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; FRAMEWORK_SEARCH_PATHS = ( diff --git a/Chronos/DispatchTimer.swift b/Chronos/DispatchTimer.swift index 5a2391a..be0bab8 100644 --- a/Chronos/DispatchTimer.swift +++ b/Chronos/DispatchTimer.swift @@ -71,7 +71,7 @@ public class DispatchTimer : NSObject, RepeatingTimer { /** The timer's execution closure. */ - public let closure: ExecutionClosure! + public let closure: RepeatedExecutionClosure! /** The number of times the execution closure has been executed. @@ -108,7 +108,7 @@ public class DispatchTimer : NSObject, RepeatingTimer { - returns: A newly created DispatchTimer object. */ - convenience public init(interval: Double, closure: ExecutionClosure) { + convenience public init(interval: Double, closure: RepeatedExecutionClosure) { let name = "\(queuePrefix).\(NSUUID().UUIDString)" let queue = dispatch_queue_create((name as NSString).UTF8String, DISPATCH_QUEUE_SERIAL) self.init(interval: interval, closure: closure, queue: queue) @@ -123,7 +123,7 @@ public class DispatchTimer : NSObject, RepeatingTimer { - returns: A newly created DispatchTimer object. */ - convenience public init(interval: Double, closure: ExecutionClosure, queue: dispatch_queue_t) { + convenience public init(interval: Double, closure: RepeatedExecutionClosure, queue: dispatch_queue_t) { self.init(interval: interval, closure: closure, queue: queue, failureClosure: nil) } @@ -137,7 +137,7 @@ public class DispatchTimer : NSObject, RepeatingTimer { - returns: A newly created DispatchTimer object. */ - public init(interval: Double, closure: ExecutionClosure, queue: dispatch_queue_t, failureClosure: FailureClosure) { + public init(interval: Double, closure: RepeatedExecutionClosure, queue: dispatch_queue_t, failureClosure: FailureClosure) { if let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue) { self.timer = timer self.valid = State.valid diff --git a/Chronos/OneShotDispatchTimer.swift b/Chronos/OneShotDispatchTimer.swift new file mode 100644 index 0000000..d7095fa --- /dev/null +++ b/Chronos/OneShotDispatchTimer.swift @@ -0,0 +1,213 @@ +// +// OneShotDispatchTimer.swift +// Chronos +// +// Copyright (c) 2015 Andrew Chun, Comyar Zaheri. All rights reserved. +// +// 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. + + +// MARK:- Imports + +import Foundation + +// Mark:- Constants and Functions + +private let queuePrefix = "com.chronos.oneShotDispatchTimer" + +// MARK:- OneShotDispatchTimer Implementation + +/** + A OneShotDispatchTimer allows you to create a Grand Central Dispatch-based timer + object. A timer waits until a certain time interval has elapsed and then + fires, executing a given closure. + + A timer has limited accuracy when determining the exact moment to fire; the + actual time at which a timer fires can potentially be a significant period + of time after the scheduled firing time. + */ +@objc +@available (iOS, introduced=8.0) +@available (OSX, introduced=10.10) +public class OneShotDispatchTimer : NSObject, OneShotTimer { + private var valid = State.invalid + private var running = State.paused + private var timer: dispatch_source_t? + private var leeway: UInt64 { + return UInt64(0.05 * delay) * NSEC_PER_SEC; + } + + // MARK: Properties + + /** + The timer's execution queue. + */ + public let queue: dispatch_queue_t! + + /** + The timer's execution delay, in seconds. + */ + public let delay: Double! + + /** + The timer's execution closure. + */ + public let closure: OneShotExecutionClosure! + + /** + true, if the timer is valid; otherwise, false. + + A timer is considered valid if it has not been canceled. + */ + public var isValid: Bool { + return (valid == State.valid) + } + + /** + true, if the timer is currently running; otherwise, false. + */ + public var isRunning: Bool { + return (running == State.running) + } + + // MARK: NSObject + override private init() { fatalError("Cannot initialize with init(). Use convenience or designated initializer.") } + deinit { cancel() } + + // MARK: Creating a OneShotDispatchTimer + + /** + Creates a OneShotDispatchTimer object. + + - parameter delay: The execution delay, in seconds. + - parameter closure: The closure to execute after the given delay. + + - returns: A newly created OneShotDispatchTimer object. + */ + convenience public init(delay: Double, closure: OneShotExecutionClosure) { + let name = "\(queuePrefix).\(NSUUID().UUIDString)" + let queue = dispatch_queue_create((name as NSString).UTF8String, DISPATCH_QUEUE_SERIAL) + self.init(delay: delay, closure: closure, queue: queue) + } + + /** + Creates a OneShotDispatchTimer object. + + - parameter delay: The execution delay, in seconds. + - parameter closure: The closure to execute after the given delay. + - parameter queue: The queue that should execute the given closure. + + - returns: A newly created OneShotDispatchTimer object. + */ + convenience public init(delay: Double, closure: OneShotExecutionClosure, queue: dispatch_queue_t) { + self.init(delay: delay, closure: closure, queue: queue, failureClosure: nil) + } + + /** + Creates a OneShotDispatchTimer object. + + - parameter delay: The execution delay, in seconds. + - parameter closure: The closure to execute after the given delay. + - parameter queue: The queue that should execute the given closure. + - parameter failureClosure: The closure to execute if creation fails. + + - returns: A newly created OneShotDispatchTimer object. + */ + public init(delay: Double, closure: OneShotExecutionClosure, queue: dispatch_queue_t, failureClosure: FailureClosure) { + + if let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue) { + self.timer = timer + self.valid = State.valid + } else { + if let failureClosure = failureClosure { + failureClosure() + } else { + print("Failed to create dispatch source for timer.") + } + } + + self.queue = queue + self.delay = delay + self.closure = closure + + super.init() + + weak var weakSelf: OneShotDispatchTimer? = self + + if let timer = timer { + dispatch_source_set_event_handler(timer) { + dispatch_source_cancel(timer); //Ensure timer only gets called once + if let strongSelf = weakSelf { + strongSelf.closure(strongSelf) + } + } + } + } + + // MARK: Using a OneShotDispatch Timer + + /** + Starts the timer. + + - parameter now: true, if the timer should fire immediately. + */ + public func start(now: Bool) { + validate() + if let timer = timer where OSAtomicCompareAndSwap32Barrier(State.paused, State.running, &running) { + dispatch_source_set_timer(timer, startTime(delay, now: now), UInt64(delay * Double(NSEC_PER_SEC)), leeway) + dispatch_resume(timer) + } + } + + /** + Pauses the timer and does not reset the count. + */ + public func pause() { + validate() + if let timer = timer where OSAtomicCompareAndSwap32Barrier(State.running, State.paused, &running) { + dispatch_suspend(timer) + } + } + + /** + Permanently cancels the timer. + + Attempting to start or pause an invalid timer is considered an error and will throw an exception. + */ + public func cancel() { + if OSAtomicCompareAndSwap32Barrier(State.valid, State.invalid, &valid) { + if let timer = timer { + if running == State.paused { + dispatch_resume(timer) + } + + running = State.paused + dispatch_source_cancel(timer) + } + } + } + + private func validate() { + if valid != State.valid { + NSException(name: NSInternalInconsistencyException, + reason: "Attempting to use invalid OneShotDispatchTimer", + userInfo: nil).raise() + } + } +} diff --git a/Chronos/OneShotTimer.swift b/Chronos/OneShotTimer.swift new file mode 100644 index 0000000..4c086a1 --- /dev/null +++ b/Chronos/OneShotTimer.swift @@ -0,0 +1,45 @@ +// +// OneShotTimer.swift +// Chronos +// +// Copyright (c) 2015 Andrew Chun, Comyar Zaheri. All rights reserved. +// +// 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. + + +// MARK:- Imports + +import Foundation + + +// MARK:- OneShotTimer Protocol + +/** + Types adopting the OneShotTimer protocol can be used to implement methods to + control a one shot timer. + */ +@objc +public protocol OneShotTimer : Timer { + + /** + The timer's execution closure. + */ + var closure: OneShotExecutionClosure! { get } + +} diff --git a/Chronos/RepeatingTimer.swift b/Chronos/RepeatingTimer.swift index 950529a..23c11d6 100644 --- a/Chronos/RepeatingTimer.swift +++ b/Chronos/RepeatingTimer.swift @@ -37,15 +37,14 @@ control a repeating timer. @objc public protocol RepeatingTimer : Timer { - /** - The timer's execution closure. - */ - var closure: ExecutionClosure! { get } - - /** The number of times the execution closure has been executed. */ var count: Int { get } + + /** + The timer's execution closure. + */ + var closure: RepeatedExecutionClosure! { get } } diff --git a/Chronos/TimerInternal.swift b/Chronos/TimerInternal.swift index 4d06d54..1cfd700 100644 --- a/Chronos/TimerInternal.swift +++ b/Chronos/TimerInternal.swift @@ -42,4 +42,11 @@ The closure to execute when the timer fires. - parameter timer: The timer that fired. - parameter count: The current invocation count. The first count is 0. */ -public typealias ExecutionClosure = ((RepeatingTimer, Int) -> Void) +public typealias RepeatedExecutionClosure = ((RepeatingTimer, Int) -> Void) + +/** + The closure to execute when the timer fires. + + - parameter timer: The timer that fired. + */ +public typealias OneShotExecutionClosure = ((OneShotTimer) -> Void) diff --git a/Chronos/VariableTimer.swift b/Chronos/VariableTimer.swift index 765b412..200f327 100644 --- a/Chronos/VariableTimer.swift +++ b/Chronos/VariableTimer.swift @@ -75,7 +75,7 @@ public class VariableTimer : NSObject, RepeatingTimer { /** The timer's execution closure. */ - public let closure: ExecutionClosure! + public let closure: RepeatedExecutionClosure! /** The timer's interval closure. @@ -119,7 +119,7 @@ public class VariableTimer : NSObject, RepeatingTimer { - returns: A newly created VariableTimer object. */ - convenience public init(closure: ExecutionClosure, intervalProvider: IntervalClosure) { + convenience public init(closure: RepeatedExecutionClosure, intervalProvider: IntervalClosure) { let name = "\(queuePrefix).\(NSUUID().UUIDString)" let queue = dispatch_queue_create((name as NSString).UTF8String, DISPATCH_QUEUE_SERIAL) self.init(closure: closure, intervalProvider: intervalProvider, queue: queue) @@ -134,7 +134,7 @@ public class VariableTimer : NSObject, RepeatingTimer { - returns: A newly created VariableTimer object. */ - convenience public init(closure: ExecutionClosure, intervalProvider: IntervalClosure, queue: dispatch_queue_t) { + convenience public init(closure: RepeatedExecutionClosure, intervalProvider: IntervalClosure, queue: dispatch_queue_t) { self.init(closure: closure, intervalProvider: intervalProvider, queue: queue, failureClosure: nil) } @@ -148,7 +148,7 @@ public class VariableTimer : NSObject, RepeatingTimer { - returns: A newly created VariableTimer object. */ - public init(closure: ExecutionClosure, intervalProvider: IntervalClosure, queue: dispatch_queue_t, failureClosure: FailureClosure) { + public init(closure: RepeatedExecutionClosure, intervalProvider: IntervalClosure, queue: dispatch_queue_t, failureClosure: FailureClosure) { if let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue) { self.timer = timer self.valid = State.valid diff --git a/ChronosTests/OneShotTimerTests.swift b/ChronosTests/OneShotTimerTests.swift new file mode 100644 index 0000000..338f08b --- /dev/null +++ b/ChronosTests/OneShotTimerTests.swift @@ -0,0 +1,154 @@ +// +// OneShotTimerTests.swift +// Chronos +// +// Copyright (c) 2015 Andrew Chun, Comyar Zaheri. All rights reserved. +// +// 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. +// + + +// MARK: - Imports + +import XCTest + + +// MARK: - OneShotDispatchTimerTests Implementation + +class OneShotDispatchTimerTests : XCTestCase { + + // 5 second timeout for async tests + var timeout: dispatch_time_t { + return dispatch_time(DISPATCH_TIME_NOW, Int64(5.0 * Double(NSEC_PER_SEC))) + } + + func testConvenienceInitializer() { + let timer = OneShotDispatchTimer(delay: 0.25, closure: { (timer: Timer) in + // nothing to do + }) + XCTAssertTrue(timer.isValid) + XCTAssertFalse(timer.isRunning) + } + + func testOneShotDispatchTimer() { + let semaphore = dispatch_semaphore_create(0) + + let timer = OneShotDispatchTimer(delay: 0.25, closure: { (timer: Timer) in + dispatch_semaphore_signal(semaphore) + }) + + XCTAssertTrue(timer.isValid) + XCTAssertFalse(timer.isRunning) + + timer.start(true) + + dispatch_semaphore_wait(semaphore, timeout) + + timer.cancel() + } + + func testTimerDoesntRepeat() { + let expectation = expectationWithDescription("testTimerDoesntRepeat") + var count: Int = 0 + + let dispatchTimer: OneShotDispatchTimer = OneShotDispatchTimer(delay: 0.25, closure: { (timer: Timer) in + + dispatch_barrier_sync(dispatch_get_main_queue()) { + count = count + 1 + } + }) + + dispatchTimer.start(true) + + XCTAssertTrue(dispatchTimer.isValid) + XCTAssertTrue(dispatchTimer.isRunning) + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(3 * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), { + XCTAssertEqual(count, 1) + expectation.fulfill() + }) + + waitForExpectationsWithTimeout(5) { error in + if let error = error { + XCTFail("Error: \(error.localizedDescription)") + } + } + } + + func testStartPassCancel() { + let dispatchTimer: OneShotDispatchTimer = OneShotDispatchTimer(delay: 0.25, closure: { (timer: Timer) in + + }) + + XCTAssertTrue(dispatchTimer.isValid) + XCTAssertFalse(dispatchTimer.isRunning) + + dispatchTimer.start(true) + + XCTAssertTrue(dispatchTimer.isValid) + XCTAssertTrue(dispatchTimer.isRunning) + + dispatchTimer.pause() + + XCTAssertTrue(dispatchTimer.isValid) + XCTAssertFalse(dispatchTimer.isRunning) + + dispatchTimer.cancel() + + XCTAssertFalse(dispatchTimer.isValid) + XCTAssertFalse(dispatchTimer.isRunning) + } + + func testIsRunning() { + let dispatchTimer: OneShotDispatchTimer = OneShotDispatchTimer(delay: 0.25, closure: { (timer: Timer) in + + }) + + dispatchTimer.start(true) + + XCTAssertTrue(dispatchTimer.isRunning) + } + + func testIsNotRunning() { + let dispatchTimer: OneShotDispatchTimer = OneShotDispatchTimer(delay: 0.25, closure: { (timer: Timer) in + + }) + + XCTAssertFalse(dispatchTimer.isRunning) + } + + func testIsValid() { + let dispatchTimer: OneShotDispatchTimer = OneShotDispatchTimer(delay: 0.25, closure: { (timer: Timer) in + + }) + + XCTAssertTrue(dispatchTimer.isValid) + } + + func testIsNotValid() { + let dispatchTimer: OneShotDispatchTimer = OneShotDispatchTimer(delay: 0.25, closure: { (timer: Timer) in + + }) + + dispatchTimer.start(true) + dispatchTimer.cancel() + + XCTAssertFalse(dispatchTimer.isValid) + } +} From 26c5d1a3ffedcb5675d4d804554504f32b787dc6 Mon Sep 17 00:00:00 2001 From: Jean Azzopardi Date: Wed, 10 Aug 2016 19:12:52 +0200 Subject: [PATCH 2/2] Uses correct cancel method when cancelling OneShotDispatchTimer --- Chronos/OneShotDispatchTimer.swift | 2 +- ChronosTests/OneShotTimerTests.swift | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Chronos/OneShotDispatchTimer.swift b/Chronos/OneShotDispatchTimer.swift index d7095fa..bca3a8d 100644 --- a/Chronos/OneShotDispatchTimer.swift +++ b/Chronos/OneShotDispatchTimer.swift @@ -152,8 +152,8 @@ public class OneShotDispatchTimer : NSObject, OneShotTimer { if let timer = timer { dispatch_source_set_event_handler(timer) { - dispatch_source_cancel(timer); //Ensure timer only gets called once if let strongSelf = weakSelf { + strongSelf.cancel() //Ensure timer only gets called once strongSelf.closure(strongSelf) } } diff --git a/ChronosTests/OneShotTimerTests.swift b/ChronosTests/OneShotTimerTests.swift index 338f08b..282f814 100644 --- a/ChronosTests/OneShotTimerTests.swift +++ b/ChronosTests/OneShotTimerTests.swift @@ -60,6 +60,8 @@ class OneShotDispatchTimerTests : XCTestCase { dispatch_semaphore_wait(semaphore, timeout) + XCTAssertFalse(timer.isRunning) + timer.cancel() }