Skip to content

Commit

Permalink
add AnyScheduler
Browse files Browse the repository at this point in the history
  • Loading branch information
GlebRadchenko committed Jan 16, 2021
1 parent 9a375e1 commit c648c3e
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 14 deletions.
23 changes: 18 additions & 5 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,38 @@
import PackageDescription

let package = Package(
name: "scheduler-kit",
name: "SchedulerKit",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.watchOS(.v6),
.tvOS(.v13)
],
products: [
.library(name: "scheduler-kit", targets: ["scheduler-kit"]),
.library(
name: "SchedulerKit",
type: .dynamic,
targets: ["SchedulerKit"]
),
.library(
name: "SchedulerKitTestUtils",
type: .dynamic,
targets: ["SchedulerKitTestUtils"]
),
],
dependencies: [],
targets: [
.target(
name: "scheduler-kit",
name: "SchedulerKit",
dependencies: []
),
.target(
name: "SchedulerKitTestUtils",
dependencies: ["SchedulerKit"]
),
.testTarget(
name: "scheduler-kitTests",
dependencies: ["scheduler-kit"]
name: "SchedulerKitTests",
dependencies: ["SchedulerKit", "SchedulerKitTestUtils"]
),
]
)
19 changes: 19 additions & 0 deletions Sources/SchedulerKit/AnyScheduler+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// AnyScheduler+Extensions.swift
//
//
// Created by Gleb Radchenko on 16.01.21.
//

import Combine
import Foundation

public extension AnyScheduler where S: DispatchQueue {
static var main: AnyScheduler<DispatchQueue> {
DispatchQueue.main.eraseToAnyScheduler()
}

static func global(qos: DispatchQoS.QoSClass = .default) -> AnyScheduler<DispatchQueue> {
DispatchQueue.global(qos: qos).eraseToAnyScheduler()
}
}
51 changes: 51 additions & 0 deletions Sources/SchedulerKit/AnyScheduler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// AnyScheduler.swift
//
//
// Created by Gleb Radchenko on 16.01.21.
//

import Combine
import Foundation

open class AnyScheduler<S: Scheduler>: AnySchedulerBase<S.SchedulerTimeType, S.SchedulerOptions> {}
public typealias DispatchQueueAnyScheduler = AnyScheduler<DispatchQueue>

/// Type erasure for Combine's Scheduler
open class AnySchedulerBase<Time: Strideable, Options>: Scheduler where Time.Stride: SchedulerTimeIntervalConvertible {
public typealias SchedulerTimeType = Time
public typealias SchedulerOptions = Options

private var _now: () -> Time
public var now: Time { _now() }

private var _minimumTolerance: () -> Time.Stride
public var minimumTolerance: Time.Stride { _minimumTolerance() }

private var _schedule: (_ options: Options?, _ action: @escaping () -> Void) -> Void
public func schedule(options: Options?, _ action: @escaping () -> Void) {
_schedule(options, action)
}

private var _scheduleAfter: (_ date: Time, _ tolerance: Time.Stride, _ options: SchedulerOptions?, _ action: @escaping () -> Void) -> Void
public func schedule(after date: Time, tolerance: Time.Stride, options: SchedulerOptions?, _ action: @escaping () -> Void) {
_scheduleAfter(date, tolerance, options, action)
}

private var _scheduleAfterCancellable: (_ date: SchedulerTimeType, _ interval: SchedulerTimeType.Stride, _ tolerance: SchedulerTimeType.Stride, _ options: SchedulerOptions?, _ action: @escaping () -> Void) -> Cancellable
public func schedule(after date: SchedulerTimeType, interval: SchedulerTimeType.Stride, tolerance: SchedulerTimeType.Stride, options: SchedulerOptions?, _ action: @escaping () -> Void) -> Cancellable {
_scheduleAfterCancellable(date, interval, tolerance, options, action)
}

public init<S: Scheduler>(scheduler: S) where S.SchedulerTimeType == Time, S.SchedulerOptions == Options {
_now = { scheduler.now }
_minimumTolerance = { scheduler.minimumTolerance }
_schedule = { options, action in scheduler.schedule(options: options, action) }
_scheduleAfter = { date, tolerance, options, action in
scheduler.schedule(after: date, tolerance: tolerance, options: options, action)
}
_scheduleAfterCancellable = { date, interval, tolerance, options, action in
scheduler.schedule(after: date, interval: interval, tolerance: tolerance, options: options, action)
}
}
}
15 changes: 15 additions & 0 deletions Sources/SchedulerKit/Scheduler+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// Scheduler+Extensions.swift
//
//
// Created by Gleb Radchenko on 16.01.21.
//

import Combine
import Foundation

public extension Scheduler {
func eraseToAnyScheduler<S: Scheduler>() -> AnyScheduler<S> where S.SchedulerOptions == SchedulerOptions, S.SchedulerTimeType == SchedulerTimeType {
AnyScheduler(scheduler: self)
}
}
16 changes: 16 additions & 0 deletions Sources/SchedulerKitTestUtils/AnyScheduler+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// AnyScheduler+Extensions.swift
//
//
// Created by Gleb Radchenko on 16.01.21.
//

import Combine
import Foundation
import SchedulerKit

public extension AnyScheduler where S: DispatchQueue {
static var test: AnyScheduler<DispatchQueue> {
TestScheduler().eraseToAnyScheduler()
}
}
111 changes: 111 additions & 0 deletions Sources/SchedulerKitTestUtils/TestScheduler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//
// TestScheduler.swift
//
//
// Created by Gleb Radchenko on 16.01.21.
//

import Combine
import Foundation
import SchedulerKit

open class TestScheduler: Scheduler {
public typealias SchedulerTimeType = DispatchQueue.SchedulerTimeType
public typealias SchedulerOptions = DispatchQueue.SchedulerOptions

public init() {}

/// For delayed actions with interval scheduler needs to be advanced before action execution
public var enableImmediateExecutionIfPossible = true

public func advance(by interval: DispatchQueue.SchedulerTimeType.Stride) {
_now = _now.advanced(by: interval)
}

public var _now: DispatchQueue.SchedulerTimeType = .init(.now())
public var now: DispatchQueue.SchedulerTimeType { _now }

public var _minimumTolerance: DispatchQueue.SchedulerTimeType.Stride = .zero
public var minimumTolerance: DispatchQueue.SchedulerTimeType.Stride { _minimumTolerance }

public var immediateAction: [ImmediateAction] = []
public func schedule(options: DispatchQueue.SchedulerOptions?, _ action: @escaping () -> Void) {
immediateAction.append(
ImmediateAction(options: options, action: action)
)

if enableImmediateExecutionIfPossible {
action()
}
}

public var delayedActions: [DelayedAction] = []
public func schedule(
after: SchedulerTimeType,
tolerance: SchedulerTimeType.Stride,
options: SchedulerOptions?,
_ action: @escaping () -> Void
) {
delayedActions.append(
DelayedAction(
after: after,
tolerance: tolerance,
options: options,
action: action
)
)

if enableImmediateExecutionIfPossible {
action()
}
}

public var delayedIntervalActions: [DelayedIntervalAction] = []
public func schedule(
after: SchedulerTimeType,
interval: SchedulerTimeType.Stride,
tolerance: SchedulerTimeType.Stride,
options : SchedulerOptions?,
_ action: @escaping () -> Void
) -> Cancellable {
let delayedIntervalAction = DelayedIntervalAction(
after: after,
interval: interval,
tolerance: tolerance,
options: options,
action: action
)
delayedIntervalActions.append(delayedIntervalAction)
return AnyCancellable { [weak self] in
self?.delayedIntervalActions.removeAll(where: { $0.uuid == delayedIntervalAction.uuid })
}
}
}

public extension TestScheduler {
struct ImmediateAction {
fileprivate let uuid = UUID()

public let options: DispatchQueue.SchedulerOptions?
public let action: () -> Void
}

struct DelayedAction {
fileprivate let uuid = UUID()

public let after: SchedulerTimeType
public let tolerance: SchedulerTimeType.Stride
public let options: SchedulerOptions?
public let action: () -> Void
}

struct DelayedIntervalAction {
fileprivate let uuid = UUID()

public let after: SchedulerTimeType
public let interval: SchedulerTimeType.Stride
public let tolerance: SchedulerTimeType.Stride
public let options: SchedulerOptions?
public let action: () -> Void
}
}
3 changes: 0 additions & 3 deletions Sources/scheduler-kit/scheduler_kit.swift

This file was deleted.

4 changes: 2 additions & 2 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import XCTest

import scheduler_kitTests
import SchedulerKitTests

var tests = [XCTestCaseEntry]()
tests += scheduler_kitTests.allTests()
tests += SchedulerKitTests.allTests()
XCTMain(tests)
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import XCTest
@testable import scheduler_kit
@testable import SchedulerKit

final class scheduler_kitTests: XCTestCase {
final class SchedulerKitTests: XCTestCase {
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
XCTAssertEqual(scheduler_kit().text, "Hello, World!")
XCTAssertTrue(true)
}

static var allTests = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import XCTest
#if !canImport(ObjectiveC)
public func allTests() -> [XCTestCaseEntry] {
return [
testCase(scheduler_kitTests.allTests),
testCase(SchedulerKitTests.allTests),
]
}
#endif

0 comments on commit c648c3e

Please sign in to comment.