-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement
FoundationExtensions
package
- Loading branch information
1 parent
0666ed5
commit 8863b07
Showing
9 changed files
with
206 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// 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: "FoundationExtensions", | ||
platforms: [ | ||
.macOS(.v10_10), | ||
.iOS(.v8), | ||
.watchOS(.v2), | ||
.tvOS(.v9), | ||
], | ||
products: [ | ||
.library( | ||
name: "FoundationExtensions", | ||
targets: ["FoundationExtensions"] | ||
), | ||
], | ||
targets: [ | ||
.target( | ||
name: "FoundationExtensions", | ||
dependencies: [] | ||
), | ||
.testTarget( | ||
name: "FoundationExtensionsTests", | ||
dependencies: ["FoundationExtensions"] | ||
), | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import Foundation | ||
|
||
public extension Array { | ||
/// Safe getting element from array by index. | ||
subscript(safe index: Int) -> Element? { | ||
(0 ..< count).contains(index) ? self[index] : nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import Foundation | ||
|
||
/// Represent general interface of `Debouncable` object. | ||
public protocol Debouncable: AnyObject { | ||
typealias ActionHandler = () -> Void | ||
|
||
/// The `Bool` value that indicates that closure is running. | ||
var isRunning: Bool { get } | ||
|
||
/// Execute closure with delay. | ||
/// | ||
/// - Parameters: | ||
/// - delay: The amount of time (measured in seconds) to wait before beginning the action. | ||
/// Specify a value of 0 to begin the action immediately. | ||
/// - action: A block object to be executed. | ||
func run(delay: TimeInterval, action: @escaping ActionHandler) | ||
|
||
/// Cancel all running tasks. | ||
func cancel() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import Foundation | ||
|
||
public final class Debouncer: Debouncable { | ||
// MARK: Public | ||
|
||
public var isRunning: Bool { | ||
!(dispatchWorkItem?.isCancelled ?? true) | ||
} | ||
|
||
public func run(delay: TimeInterval, action: @escaping ActionHandler) { | ||
cancel() | ||
_run(delay: delay, action) | ||
} | ||
|
||
public func cancel() { | ||
dispatchWorkItem?.cancel() | ||
} | ||
|
||
// MARK: Private | ||
|
||
private var dispatchWorkItem: DispatchWorkItem? | ||
|
||
private func _run(delay: TimeInterval, _ closure: @escaping ActionHandler) { | ||
let dispatchWorkItem = DispatchWorkItem(block: closure) | ||
|
||
DispatchQueue.main.asyncAfter( | ||
deadline: .now() + delay, | ||
execute: dispatchWorkItem | ||
) | ||
|
||
self.dispatchWorkItem = dispatchWorkItem | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import Foundation | ||
|
||
public extension Thread { | ||
/// Name of the current thread. | ||
var threadName: String { | ||
if let currentOperationQueue = OperationQueue.current?.name { | ||
return "OperationQueue: \(currentOperationQueue)" | ||
} else if let underlyingDispatchQueue = OperationQueue.current?.underlyingQueue?.label { | ||
return "DispatchQueue: \(underlyingDispatchQueue)" | ||
} else { | ||
let name = __dispatch_queue_get_label(nil) | ||
return String(cString: name, encoding: .utf8) ?? Thread.current.description | ||
} | ||
} | ||
} | ||
|
||
/// Run closure on main thread. | ||
/// | ||
/// - Parameter run: A completion block that will executed on main thread. | ||
public func onMain(run: @escaping () -> Void) { | ||
DispatchQueue.main.async { | ||
run() | ||
} | ||
} | ||
|
||
/// Background serial queue. | ||
let backgroundThread = DispatchQueue(label: "com.foundation-extensions.thread") | ||
|
||
/// Run closure on background | ||
/// | ||
/// - Parameter run: A completion block that will executed on background thread. | ||
public func onBackground(run: @escaping () -> Void) { | ||
backgroundThread.async { | ||
run() | ||
} | ||
} | ||
|
||
/// Execute closure on main thread. | ||
/// | ||
/// - Parameter work: A block based object to be executed on main thread. | ||
public func ensureMainThread(execute work: @escaping @convention(block) () -> Swift.Void) { | ||
if Thread.isMainThread { | ||
work() | ||
} else { | ||
onMain { | ||
work() | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
@testable import FoundationExtensions | ||
import XCTest | ||
|
||
final class ArrayTests: XCTestCase { | ||
func testThatGettingElementByIndexIsSafe() { | ||
let array = [1, 2, 3] | ||
XCTAssertNil(array[safe: 5], "Element at index 5 should be nil") | ||
XCTAssertEqual(array[safe: 1], 2, "Elements should have the same values") | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
Tests/FoundationExtensionsTests/Debouncer/DebouncerTests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
@testable import FoundationExtensions | ||
import XCTest | ||
|
||
final class DebouncerTests: XCTestCase { | ||
// MARK: Internal | ||
|
||
func testThatDebouncerExecuteClosure() { | ||
let delay: TimeInterval = 2.0 | ||
let startTime = CFAbsoluteTimeGetCurrent() | ||
|
||
let expectation = XCTestExpectation(description: "Run delayed closure") | ||
|
||
debouncer.run(delay: delay) { | ||
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime | ||
XCTAssertEqual(timeElapsed, delay, accuracy: 0.1) | ||
|
||
expectation.fulfill() | ||
} | ||
|
||
wait(for: [expectation], timeout: 3.0) | ||
} | ||
|
||
func testThatDebouncerCancelExecutedClosure() { | ||
let delay: TimeInterval = 0.0 | ||
|
||
debouncer.run(delay: delay) { | ||
XCTFail("The closure must not be executed") | ||
} | ||
|
||
debouncer.cancel() | ||
|
||
XCTAssertFalse(debouncer.isRunning) | ||
} | ||
|
||
// MARK: Private | ||
|
||
private let debouncer: Debouncable = Debouncer() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
@testable import FoundationExtensions | ||
import XCTest | ||
|
||
final class ThreadTests: XCTestCase { | ||
func testThatThreadRunningClosureOnMainThread() { | ||
onMain { | ||
XCTAssertTrue(Thread.isMainThread, "The closure must be running on the main thread") | ||
} | ||
} | ||
|
||
func testThatThreadRunningClosureOnBackgroundThread() { | ||
onBackground { | ||
XCTAssertFalse(!Thread.isMainThread, "The closure must be running on the background thread") | ||
XCTAssertEqual(Thread.current.threadName, backgroundThread.label, "The threads must be equal") | ||
} | ||
} | ||
} |