From 3f0127eb989f33eda0dcf3c592b86b9fad2d48c5 Mon Sep 17 00:00:00 2001 From: Vlad Suhomlinov <> Date: Sun, 15 Aug 2021 16:15:43 +0300 Subject: [PATCH 1/6] feat: add base classes --- .gitignore | 2 +- Package.swift | 31 ++++ Sources/TITimer/Enums/TimerRunMode.swift | 15 ++ Sources/TITimer/Enums/TimerType.swift | 23 +++ .../TITimer/Protocols/IInvalidatable.swift | 31 ++++ Sources/TITimer/Protocols/ITimer.swift | 14 ++ Sources/TITimer/TITimer.swift | 154 ++++++++++++++++++ Tests/TITimerTests/TITimerTests.swift | 8 + 8 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 Package.swift create mode 100644 Sources/TITimer/Enums/TimerRunMode.swift create mode 100644 Sources/TITimer/Enums/TimerType.swift create mode 100644 Sources/TITimer/Protocols/IInvalidatable.swift create mode 100644 Sources/TITimer/Protocols/ITimer.swift create mode 100644 Sources/TITimer/TITimer.swift create mode 100644 Tests/TITimerTests/TITimerTests.swift diff --git a/.gitignore b/.gitignore index 330d167..66a2892 100644 --- a/.gitignore +++ b/.gitignore @@ -44,7 +44,7 @@ playground.xcworkspace # # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata # hence it is not needed unless you have added a package configuration file to your project -# .swiftpm +.swiftpm/ .build/ diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..eca0475 --- /dev/null +++ b/Package.swift @@ -0,0 +1,31 @@ +// swift-tools-version:5.3 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "TITimer", + platforms: [ + .iOS(.v13) + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "TITimer", + targets: ["TITimer"]), + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + // .package(url: /* package url */, from: "1.0.0"), + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "TITimer", + dependencies: []), + .testTarget( + name: "TITimerTests", + dependencies: ["TITimer"]), + ] +) diff --git a/Sources/TITimer/Enums/TimerRunMode.swift b/Sources/TITimer/Enums/TimerRunMode.swift new file mode 100644 index 0000000..ccdb322 --- /dev/null +++ b/Sources/TITimer/Enums/TimerRunMode.swift @@ -0,0 +1,15 @@ +// +// TimerRunMode.swift +// +// +// Created by Vlad Suhomlinov on 15.08.2021. +// + +enum TimerRunMode { + + // Время таймера изменяется только при активной работе приложения + case onlyActive + + // Время таймера изменяется и при активном и свернутом состоянии приложения + case activeAndBackground +} diff --git a/Sources/TITimer/Enums/TimerType.swift b/Sources/TITimer/Enums/TimerType.swift new file mode 100644 index 0000000..0d91148 --- /dev/null +++ b/Sources/TITimer/Enums/TimerType.swift @@ -0,0 +1,23 @@ +// +// TimerType.swift +// +// +// Created by Vlad Suhomlinov on 15.08.2021. +// + +import UIKit + +enum TimerType { + + // Запуск таймера на определенной GCD очереди + case dispatchSourceTimer(queue: DispatchQueue) + + // Запуск таймера на определенном режиме Runloop + case runloopTimer(runloop: RunLoop = .main, mode: RunLoop.Mode) + + // Запуск таймера, синхронизированного с частотой кадром девайса, на определенном режиме Runloop + case caDisplayLink(runloop: RunLoop = .main, mode: RunLoop.Mode) + + // Собственная реализация таймера + case custom(ITimer) +} diff --git a/Sources/TITimer/Protocols/IInvalidatable.swift b/Sources/TITimer/Protocols/IInvalidatable.swift new file mode 100644 index 0000000..588f2b4 --- /dev/null +++ b/Sources/TITimer/Protocols/IInvalidatable.swift @@ -0,0 +1,31 @@ +// +// IInvalidatable.swift +// +// +// Created by Vlad Suhomlinov on 15.08.2021. +// + +import UIKit + +protocol IInvalidatable: AnyObject { + + // Уничтожить объект + func invalidate() +} + +// MARK: - IInvalidatable + +extension Timer: IInvalidatable { } + +extension CADisplayLink: IInvalidatable { } + +extension DispatchSource: IInvalidatable { + + func invalidate() { + setEventHandler(handler: nil) + + if !isCancelled { + cancel() + } + } +} diff --git a/Sources/TITimer/Protocols/ITimer.swift b/Sources/TITimer/Protocols/ITimer.swift new file mode 100644 index 0000000..adee328 --- /dev/null +++ b/Sources/TITimer/Protocols/ITimer.swift @@ -0,0 +1,14 @@ +// +// ITimer.swift +// +// +// Created by Vlad Suhomlinov on 15.08.2021. +// + +import Foundation + +protocol ITimer: IInvalidatable { + + // Запустить работу таймера + func start(with interval: TimeInterval) +} diff --git a/Sources/TITimer/TITimer.swift b/Sources/TITimer/TITimer.swift new file mode 100644 index 0000000..647ffb7 --- /dev/null +++ b/Sources/TITimer/TITimer.swift @@ -0,0 +1,154 @@ +import UIKit + +final class TITimer { + + private let mode: TimerRunMode + private let type: TimerType + + private var sourceTimer: IInvalidatable? + + private var enterBackgroundDate: Date? + private var interval: TimeInterval = 0 + private var elapsedTime: TimeInterval = 0 { + didSet { + eventHandler?(elapsedTime) + } + } + + var eventHandler: ((TimeInterval) -> Void)? + + // MARK: - Initialization + + init(type: TimerType, mode: TimerRunMode) { + self.mode = mode + self.type = type + + if mode == .activeAndBackground { + addObserver() + } + } + + deinit { + if mode == .activeAndBackground { + removeObserver() + } + } + + // MARK: - Public + + func start(with interval: TimeInterval) { + invalidate() + + self.interval = interval + + switch type { + case let .dispatchSourceTimer(queue): + sourceTimer = startDispatchSourceTimer(interval: interval, queue: queue) + + case let .runloopTimer(runloop, mode): + sourceTimer = startTimer(interval: interval, runloop: runloop, mode: mode) + + case let .caDisplayLink(runloop, mode): + sourceTimer = startCADisplayLink(runloop: runloop, mode: mode) + + case let .custom(timer): + sourceTimer = timer + + timer.start(with: interval) + } + } + + func invalidate() { + sourceTimer?.invalidate() + sourceTimer = nil + } + + // MARK: - Private + + @objc private func handleSourceUpdate() { + guard enterBackgroundDate == nil else { + return + } + + elapsedTime += interval + } +} + +// MARK: - NotificationCenter + +private extension TITimer { + + func addObserver() { + NotificationCenter.default.addObserver(self, + selector: #selector(willEnterForegroundNotification), + name: UIApplication.willEnterForegroundNotification, + object: nil) + + NotificationCenter.default.addObserver(self, + selector: #selector(didEnterBackgroundNotification), + name: UIApplication.didEnterBackgroundNotification, + object: nil) + } + + func removeObserver() { + NotificationCenter.default.removeObserver(self) + NotificationCenter.default.removeObserver(self) + } + + @objc func willEnterForegroundNotification() { + guard let unwrappedEnterBackgroundDate = enterBackgroundDate else { + return + } + + let timeInBackground = -unwrappedEnterBackgroundDate.timeIntervalSinceNow + + enterBackgroundDate = nil + elapsedTime += timeInBackground + } + + @objc func didEnterBackgroundNotification() { + enterBackgroundDate = Date() + } +} + +// MARK: - DispatchSourceTimer + +private extension TITimer { + + func startDispatchSourceTimer(interval: TimeInterval, queue: DispatchQueue) -> IInvalidatable? { + let timer = DispatchSource.makeTimerSource(flags: .strict, queue: queue) + timer.schedule(deadline: .distantFuture, repeating: interval) + timer.setEventHandler(handler: handleSourceUpdate) + + timer.resume() + + return timer as? DispatchSource + } +} + +// MARK: - Timer + +private extension TITimer { + + func startTimer(interval: TimeInterval, runloop: RunLoop, mode: RunLoop.Mode) -> IInvalidatable { + let timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] _ in + self?.handleSourceUpdate() + } + + runloop.add(timer, forMode: mode) + + return timer + } +} + +// MARK: - CADisplayLink + +private extension TITimer { + + func startCADisplayLink(runloop: RunLoop, mode: RunLoop.Mode) -> IInvalidatable { + let timer = CADisplayLink(target: self, selector: #selector(handleSourceUpdate)) + timer.add(to: runloop, forMode: mode) + + return timer + } +} diff --git a/Tests/TITimerTests/TITimerTests.swift b/Tests/TITimerTests/TITimerTests.swift new file mode 100644 index 0000000..a616741 --- /dev/null +++ b/Tests/TITimerTests/TITimerTests.swift @@ -0,0 +1,8 @@ + import XCTest + @testable import TITimer + + final class TITimerTests: XCTestCase { + func testExample() { + + } + } From 179f9a21bd8537a7efd403ea5db82e0cc6b8f3af Mon Sep 17 00:00:00 2001 From: Vlad Suhomlinov <> Date: Sun, 15 Aug 2021 18:34:23 +0300 Subject: [PATCH 2/6] =?UTF-8?q?refactor:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20unit=20=D1=82=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/TITimer/Enums/TimerRunMode.swift | 2 +- Sources/TITimer/Enums/TimerType.swift | 7 +- .../TITimer/Protocols/IInvalidatable.swift | 6 +- Sources/TITimer/Protocols/ITimer.swift | 11 +- Sources/TITimer/TITimer.swift | 57 ++++----- Tests/TITimerTests/TITimerTests.swift | 112 +++++++++++++++++- 6 files changed, 152 insertions(+), 43 deletions(-) diff --git a/Sources/TITimer/Enums/TimerRunMode.swift b/Sources/TITimer/Enums/TimerRunMode.swift index ccdb322..0237187 100644 --- a/Sources/TITimer/Enums/TimerRunMode.swift +++ b/Sources/TITimer/Enums/TimerRunMode.swift @@ -5,7 +5,7 @@ // Created by Vlad Suhomlinov on 15.08.2021. // -enum TimerRunMode { +public enum TimerRunMode { // Время таймера изменяется только при активной работе приложения case onlyActive diff --git a/Sources/TITimer/Enums/TimerType.swift b/Sources/TITimer/Enums/TimerType.swift index 0d91148..9cb7e17 100644 --- a/Sources/TITimer/Enums/TimerType.swift +++ b/Sources/TITimer/Enums/TimerType.swift @@ -7,17 +7,14 @@ import UIKit -enum TimerType { +public enum TimerType { // Запуск таймера на определенной GCD очереди case dispatchSourceTimer(queue: DispatchQueue) // Запуск таймера на определенном режиме Runloop case runloopTimer(runloop: RunLoop = .main, mode: RunLoop.Mode) - - // Запуск таймера, синхронизированного с частотой кадром девайса, на определенном режиме Runloop - case caDisplayLink(runloop: RunLoop = .main, mode: RunLoop.Mode) - + // Собственная реализация таймера case custom(ITimer) } diff --git a/Sources/TITimer/Protocols/IInvalidatable.swift b/Sources/TITimer/Protocols/IInvalidatable.swift index 588f2b4..a18ccaa 100644 --- a/Sources/TITimer/Protocols/IInvalidatable.swift +++ b/Sources/TITimer/Protocols/IInvalidatable.swift @@ -7,7 +7,7 @@ import UIKit -protocol IInvalidatable: AnyObject { +public protocol IInvalidatable: AnyObject { // Уничтожить объект func invalidate() @@ -17,11 +17,9 @@ protocol IInvalidatable: AnyObject { extension Timer: IInvalidatable { } -extension CADisplayLink: IInvalidatable { } - extension DispatchSource: IInvalidatable { - func invalidate() { + public func invalidate() { setEventHandler(handler: nil) if !isCancelled { diff --git a/Sources/TITimer/Protocols/ITimer.swift b/Sources/TITimer/Protocols/ITimer.swift index adee328..c486ea6 100644 --- a/Sources/TITimer/Protocols/ITimer.swift +++ b/Sources/TITimer/Protocols/ITimer.swift @@ -7,7 +7,16 @@ import Foundation -protocol ITimer: IInvalidatable { +public protocol ITimer: IInvalidatable { + + // Прошедшее время + var elapsedTime: TimeInterval { get } + + // Запущен таймер или нет + var isRunning: Bool { get } + + // Подписка на изменение прошедшего времени + var eventHandler: ((TimeInterval) -> Void)? { get set } // Запустить работу таймера func start(with interval: TimeInterval) diff --git a/Sources/TITimer/TITimer.swift b/Sources/TITimer/TITimer.swift index 647ffb7..575fe11 100644 --- a/Sources/TITimer/TITimer.swift +++ b/Sources/TITimer/TITimer.swift @@ -1,7 +1,7 @@ import UIKit -final class TITimer { - +public final class TITimer: ITimer { + private let mode: TimerRunMode private let type: TimerType @@ -9,17 +9,18 @@ final class TITimer { private var enterBackgroundDate: Date? private var interval: TimeInterval = 0 - private var elapsedTime: TimeInterval = 0 { - didSet { - eventHandler?(elapsedTime) - } + + public private(set) var elapsedTime: TimeInterval = 0 + + public var isRunning: Bool { + sourceTimer != nil } - var eventHandler: ((TimeInterval) -> Void)? + public var eventHandler: ((TimeInterval) -> Void)? // MARK: - Initialization - init(type: TimerType, mode: TimerRunMode) { + public init(type: TimerType, mode: TimerRunMode) { self.mode = mode self.type = type @@ -32,11 +33,13 @@ final class TITimer { if mode == .activeAndBackground { removeObserver() } + + invalidate() } // MARK: - Public - func start(with interval: TimeInterval) { + public func start(with interval: TimeInterval = 1) { invalidate() self.interval = interval @@ -48,17 +51,17 @@ final class TITimer { case let .runloopTimer(runloop, mode): sourceTimer = startTimer(interval: interval, runloop: runloop, mode: mode) - case let .caDisplayLink(runloop, mode): - sourceTimer = startCADisplayLink(runloop: runloop, mode: mode) - case let .custom(timer): sourceTimer = timer timer.start(with: interval) } + + eventHandler?(elapsedTime) } - func invalidate() { + public func invalidate() { + elapsedTime = 0 sourceTimer?.invalidate() sourceTimer = nil } @@ -71,6 +74,17 @@ final class TITimer { } elapsedTime += interval + + eventHandler?(elapsedTime) + } +} + +// MARK: - Factory + +extension TITimer { + + public static var `default`: TITimer { + .init(type: .runloopTimer(runloop: .main, mode: .default), mode: .activeAndBackground) } } @@ -104,6 +118,7 @@ private extension TITimer { enterBackgroundDate = nil elapsedTime += timeInBackground + eventHandler?(elapsedTime) } @objc func didEnterBackgroundNotification() { @@ -116,8 +131,8 @@ private extension TITimer { private extension TITimer { func startDispatchSourceTimer(interval: TimeInterval, queue: DispatchQueue) -> IInvalidatable? { - let timer = DispatchSource.makeTimerSource(flags: .strict, queue: queue) - timer.schedule(deadline: .distantFuture, repeating: interval) + let timer = DispatchSource.makeTimerSource(flags: [], queue: queue) + timer.schedule(deadline: .now() + interval, repeating: interval) timer.setEventHandler(handler: handleSourceUpdate) timer.resume() @@ -140,15 +155,3 @@ private extension TITimer { return timer } } - -// MARK: - CADisplayLink - -private extension TITimer { - - func startCADisplayLink(runloop: RunLoop, mode: RunLoop.Mode) -> IInvalidatable { - let timer = CADisplayLink(target: self, selector: #selector(handleSourceUpdate)) - timer.add(to: runloop, forMode: mode) - - return timer - } -} diff --git a/Tests/TITimerTests/TITimerTests.swift b/Tests/TITimerTests/TITimerTests.swift index a616741..e916c39 100644 --- a/Tests/TITimerTests/TITimerTests.swift +++ b/Tests/TITimerTests/TITimerTests.swift @@ -1,8 +1,110 @@ - import XCTest - @testable import TITimer +import XCTest +@testable import TITimer + +final class TITimerTests: XCTestCase { + + // MARK: - Invalidation - final class TITimerTests: XCTestCase { - func testExample() { - + func test_thanDispatchSourceTimerIsInvalidated_whenIsStopped() { + // given + let expectation = expectation(description: "DispatchSourceTimer is invalidated") + let timer = TITimer(type: .dispatchSourceTimer(queue: .main), mode: .onlyActive) + + // when + timer.start() + + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + timer.invalidate() + expectation.fulfill() + } + + wait(for: [expectation], timeout: 5) + + // then + XCTAssertFalse(timer.isRunning) + } + + func test_RunloopTimerIsInvalidated_whenIsStopped() { + // given + let expectation = expectation(description: "RunloopTimer is invalidated") + let timer = TITimer(type: .runloopTimer(runloop: .main, mode: .default), mode: .onlyActive) + + // when + timer.start() + + DispatchQueue.main.asyncAfter(deadline: .now() + 2) { + timer.invalidate() + expectation.fulfill() + } + + wait(for: [expectation], timeout: 5) + + // then + XCTAssertFalse(timer.isRunning) + + } + + // MARK: - Scheduling + + func test_thanIntervalIsCorrect_whenDispatchSourceTimerIsStopped() { + // given + let expectation = expectation(description: "DispatchSourceTimer is invalidated") + let timer = TITimer(type: .dispatchSourceTimer(queue: .main), mode: .onlyActive) + + var elapsedTimes: [TimeInterval] = [] + + timer.eventHandler = { [weak timer] in + elapsedTimes.append($0) + + if $0 == 2 { + timer?.invalidate() + expectation.fulfill() + } } + + let start = Date() + + // when + + timer.start() + + wait(for: [expectation], timeout: 5) + + // then + let end = Date() + + XCTAssertEqual(2, end.timeIntervalSince(start), accuracy: 0.5) + XCTAssertEqual(elapsedTimes, [0, 1, 2]) + } + + func test_thanIntervalIsCorrect_whenRunloopTimerIsStopped() { + // given + let expectation = expectation(description: "DispatchSourceTimer is invalidated") + let timer = TITimer(type: .runloopTimer(runloop: .current, mode: .default), mode: .onlyActive) + + var elapsedTimes: [TimeInterval] = [] + + timer.eventHandler = { [weak timer] in + elapsedTimes.append($0) + + if $0 == 2 { + timer?.invalidate() + expectation.fulfill() + } + } + + let start = Date() + + // when + + timer.start() + + wait(for: [expectation], timeout: 5) + + // then + let end = Date() + + XCTAssertEqual(2, end.timeIntervalSince(start), accuracy: 0.5) + XCTAssertEqual(elapsedTimes, [0, 1, 2]) } +} From cb0d9edc4324b9c185ba597e3fad10ef6a609009 Mon Sep 17 00:00:00 2001 From: Vlad Suhomlinov <> Date: Sun, 15 Aug 2021 18:34:41 +0300 Subject: [PATCH 3/6] =?UTF-8?q?refactor:=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20Package?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Package.swift | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Package.swift b/Package.swift index eca0475..c0ad9fd 100644 --- a/Package.swift +++ b/Package.swift @@ -9,23 +9,19 @@ let package = Package( .iOS(.v13) ], products: [ - // Products define the executables and libraries a package produces, and make them visible to other packages. .library( name: "TITimer", targets: ["TITimer"]), ], - dependencies: [ - // Dependencies declare other packages that this package depends on. - // .package(url: /* package url */, from: "1.0.0"), - ], targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "TITimer", dependencies: []), .testTarget( name: "TITimerTests", dependencies: ["TITimer"]), + ], + swiftLanguageVersions: [ + .v5 ] ) From 195e99da4cded3c7f5b804d9a01842b3eaa4f154 Mon Sep 17 00:00:00 2001 From: Vlad Suhomlinov <> Date: Sun, 15 Aug 2021 18:35:45 +0300 Subject: [PATCH 4/6] =?UTF-8?q?typo:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BE=D0=BF=D0=B5=D1=87=D0=B0?= =?UTF-8?q?=D1=82=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Tests/TITimerTests/TITimerTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/TITimerTests/TITimerTests.swift b/Tests/TITimerTests/TITimerTests.swift index e916c39..6d58dda 100644 --- a/Tests/TITimerTests/TITimerTests.swift +++ b/Tests/TITimerTests/TITimerTests.swift @@ -79,7 +79,7 @@ final class TITimerTests: XCTestCase { func test_thanIntervalIsCorrect_whenRunloopTimerIsStopped() { // given - let expectation = expectation(description: "DispatchSourceTimer is invalidated") + let expectation = expectation(description: "RunloopTimer is invalidated") let timer = TITimer(type: .runloopTimer(runloop: .current, mode: .default), mode: .onlyActive) var elapsedTimes: [TimeInterval] = [] From c6fdb287461b9026f831e049cdfa1b6a3a188474 Mon Sep 17 00:00:00 2001 From: Vlad Suhomlinov <> Date: Sun, 15 Aug 2021 18:43:02 +0300 Subject: [PATCH 5/6] =?UTF-8?q?refactor:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BE=D0=BA=D1=80=D1=83=D0=B3=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B2=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/TITimer/TITimer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/TITimer/TITimer.swift b/Sources/TITimer/TITimer.swift index 575fe11..7c2be3f 100644 --- a/Sources/TITimer/TITimer.swift +++ b/Sources/TITimer/TITimer.swift @@ -114,7 +114,7 @@ private extension TITimer { return } - let timeInBackground = -unwrappedEnterBackgroundDate.timeIntervalSinceNow + let timeInBackground = -unwrappedEnterBackgroundDate.timeIntervalSinceNow.rounded() enterBackgroundDate = nil elapsedTime += timeInBackground From fd1729dae881e5272246f66cd125eb5866e4e96b Mon Sep 17 00:00:00 2001 From: Loupehope <31570429+Loupehope@users.noreply.github.com> Date: Sun, 15 Aug 2021 19:00:17 +0300 Subject: [PATCH 6/6] Update README.md --- README.md | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 268c7f1..6d8b365 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,37 @@ # TITimer -Pretty timer ⏰ +## Pretty timer ⏰ + +The library allows you to create a timer that works either with [RunLoop](#Runloop) or with [GCD](#GCD). + +### Features + +- Track the time even while the application is in the background. For more, see - [TimerRunMode](/Sources/TITimer/Enums/TimerRunMode.swift) +- Create a timer on a personal queue or on a special Runloop mode. For more, see - [TimerType](Sources/TITimer/Enums/TimerType.swift) +- The code is covered by tests 🙂 + +### Examples + +#### RunLoop + +```swift +timer = TITimer(type: .runloopTimer(runloop: .current, mode: .default), mode: .activeAndBackground) + +timer.eventHandler = { + // handle elapsed time +} + +timer.start() +timer.invalidate() +``` +#### GCD + +```swift +timer = TITimer(type: .dispatchSourceTimer(queue: .main), mode: .activeAndBackground) + +timer.eventHandler = { + // handle elapsed time +} + +timer.start() +timer.invalidate() +```