Skip to content

Commit

Permalink
Swift Language Support: Drop <5.9, Add 6.0 (#94)
Browse files Browse the repository at this point in the history
* Swift Language Support: Drop <5.9, Add 6.0

* wip

* wip
  • Loading branch information
stephencelis authored Jun 18, 2024
1 parent 1d1d2bd commit 36f9d82
Show file tree
Hide file tree
Showing 11 changed files with 110 additions and 100 deletions.
28 changes: 9 additions & 19 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,18 @@ on:
- '*'

jobs:
library-swift-latest:
name: Library (swift-latest)
runs-on: macOS-13
library:
name: Library
runs-on: macos-14
steps:
- uses: actions/checkout@v3
- name: Select Xcode 14.3
run: sudo xcode-select -s /Applications/Xcode_14.3.app
- uses: actions/checkout@v4
- name: Select Xcode 15.4
run: sudo xcode-select -s /Applications/Xcode_15.4.app
- name: Run tests
run: make test
- name: Build for library evolution
run: make build-for-library-evolution

library-swift-5-6:
name: Library (swift-5.6)
runs-on: macOS-12
steps:
- uses: actions/checkout@v3
- name: Select Xcode 13.4.1
run: sudo xcode-select -s /Applications/Xcode_13.4.1.app
- name: Run tests
run: make test

windows:
name: Windows
strategy:
Expand All @@ -41,9 +31,9 @@ jobs:
steps:
- uses: compnerd/gha-setup-swift@main
with:
branch: swift-5.8-release
tag: 5.8-RELEASE
branch: swift-5.10-release
tag: 5.10-RELEASE

- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Run tests
run: swift test -c ${{ matrix.config }}
9 changes: 8 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.5
// swift-tools-version: 5.9

import PackageDescription

Expand Down Expand Up @@ -36,3 +36,10 @@ let package = Package(
),
]
)

for target in package.targets {
target.swiftSettings = target.swiftSettings ?? []
target.swiftSettings!.append(contentsOf: [
.enableExperimentalFeature("StrictConcurrency")
])
}
39 changes: 39 additions & 0 deletions Package@swift-6.0.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// swift-tools-version: 6.0

import PackageDescription

let package = Package(
name: "combine-schedulers",
platforms: [
.iOS(.v13),
.macOS(.v10_15),
.tvOS(.v13),
.watchOS(.v6),
],
products: [
.library(
name: "CombineSchedulers",
targets: ["CombineSchedulers"]
)
],
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-concurrency-extras", from: "1.0.0"),
.package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.0.0"),
],
targets: [
.target(
name: "CombineSchedulers",
dependencies: [
.product(name: "ConcurrencyExtras", package: "swift-concurrency-extras"),
.product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"),
]
),
.testTarget(
name: "CombineSchedulersTests",
dependencies: [
"CombineSchedulers"
]
),
],
swiftLanguageVersions: [.v6]
)
23 changes: 7 additions & 16 deletions Sources/CombineSchedulers/AnyScheduler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,10 @@
/// in classes, functions, etc. without needing to introduce a generic, which can help simplify
/// the code and reduce implementation details from leaking out.
///
public struct AnyScheduler<SchedulerTimeType, SchedulerOptions>: Scheduler, @unchecked Sendable
where
SchedulerTimeType: Strideable,
SchedulerTimeType.Stride: SchedulerTimeIntervalConvertible
{

public struct AnyScheduler<
SchedulerTimeType: Strideable, SchedulerOptions
>: Scheduler, @unchecked Sendable
where SchedulerTimeType.Stride: SchedulerTimeIntervalConvertible {
private let _minimumTolerance: () -> SchedulerTimeType.Stride
private let _now: () -> SchedulerTimeType
private let _scheduleAfterIntervalToleranceOptionsAction:
Expand Down Expand Up @@ -193,12 +191,9 @@
///
/// - Parameters:
/// - scheduler: A scheduler to wrap with a type-eraser.
public init<S>(
public init<S: Scheduler<SchedulerTimeType>>(
_ scheduler: S
)
where
S: Scheduler, S.SchedulerTimeType == SchedulerTimeType, S.SchedulerOptions == SchedulerOptions
{
) where S.SchedulerOptions == SchedulerOptions {
self._now = { scheduler.now }
self._minimumTolerance = { scheduler.minimumTolerance }
self._scheduleAfterToleranceOptionsAction = scheduler.schedule
Expand Down Expand Up @@ -251,11 +246,7 @@
}
}

extension AnyScheduler
where
SchedulerTimeType == DispatchQueue.SchedulerTimeType,
SchedulerOptions == DispatchQueue.SchedulerOptions
{
extension AnySchedulerOf<DispatchQueue> {
/// A type-erased main dispatch queue.
public static var main: Self {
DispatchQueue.main.eraseToAnyScheduler()
Expand Down
12 changes: 6 additions & 6 deletions Sources/CombineSchedulers/Concurrency.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#if canImport(Combine)
import Combine
@preconcurrency import Combine

extension Scheduler {
/// Suspends the current task for at least the given duration.
Expand All @@ -8,7 +8,7 @@
///
/// This function doesn't block the scheduler.
///
/// ```
/// ```swift
/// try await in scheduler.sleep(for: .seconds(1))
/// ```
///
Expand All @@ -35,7 +35,7 @@
///
/// This function doesn't block the scheduler.
///
/// ```
/// ```swift
/// try await in scheduler.sleep(until: scheduler.now + .seconds(1))
/// ```

Expand All @@ -59,7 +59,7 @@
///
/// If the task is cancelled, the sequence will terminate.
///
/// ```
/// ```swift
/// for await instant in scheduler.timer(interval: .seconds(1)) {
/// print("now:", instant)
/// }
Expand All @@ -76,7 +76,7 @@
tolerance: SchedulerTimeType.Stride = .zero,
options: SchedulerOptions? = nil
) -> AsyncStream<SchedulerTimeType> {
.init { continuation in
AsyncStream { continuation in
let cancellable = self.schedule(
after: self.now.advanced(by: interval),
interval: interval,
Expand All @@ -96,7 +96,7 @@

/// Measure the elapsed time to execute a closure.
///
/// ```
/// ```swift
/// let elapsed = scheduler.measure {
/// someWork()
/// }
Expand Down
34 changes: 9 additions & 25 deletions Sources/CombineSchedulers/ImmediateScheduler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,8 @@
/// > `ImmediateScheduler` will not schedule this work in a defined way. Use a `TestScheduler`
/// > instead to capture your publisher's timing behavior.
///
public struct ImmediateScheduler<SchedulerTimeType, SchedulerOptions>: Scheduler
where
SchedulerTimeType: Strideable,
SchedulerTimeType.Stride: SchedulerTimeIntervalConvertible
{

public struct ImmediateScheduler<SchedulerTimeType: Strideable, SchedulerOptions>: Scheduler
where SchedulerTimeType.Stride: SchedulerTimeIntervalConvertible {
public let minimumTolerance: SchedulerTimeType.Stride = .zero
public let now: SchedulerTimeType

Expand Down Expand Up @@ -146,59 +142,47 @@
/// An immediate scheduler that can substitute itself for a dispatch queue.
public static var immediate: ImmediateSchedulerOf<DispatchQueue> {
// NB: `DispatchTime(uptimeNanoseconds: 0) == .now())`. Use `1` for consistency.
.init(now: .init(.init(uptimeNanoseconds: 1)))
ImmediateScheduler(now: DispatchQueue.SchedulerTimeType(DispatchTime(uptimeNanoseconds: 1)))
}
}

extension UIScheduler {
/// An immediate scheduler that can substitute itself for a UI scheduler.
public static var immediate: ImmediateSchedulerOf<UIScheduler> {
// NB: `DispatchTime(uptimeNanoseconds: 0) == .now())`. Use `1` for consistency.
.init(now: .init(.init(uptimeNanoseconds: 1)))
ImmediateScheduler(now: UIScheduler.SchedulerTimeType(DispatchTime(uptimeNanoseconds: 1)))
}
}

extension OperationQueue {
/// An immediate scheduler that can substitute itself for an operation queue.
public static var immediate: ImmediateSchedulerOf<OperationQueue> {
.init(now: .init(.init(timeIntervalSince1970: 0)))
ImmediateScheduler(now: OperationQueue.SchedulerTimeType(Date(timeIntervalSince1970: 0)))
}
}

extension RunLoop {
/// An immediate scheduler that can substitute itself for a run loop.
public static var immediate: ImmediateSchedulerOf<RunLoop> {
.init(now: .init(.init(timeIntervalSince1970: 0)))
ImmediateScheduler(now: RunLoop.SchedulerTimeType(Date(timeIntervalSince1970: 0)))
}
}

extension AnyScheduler
where
SchedulerTimeType == DispatchQueue.SchedulerTimeType,
SchedulerOptions == DispatchQueue.SchedulerOptions
{
extension AnySchedulerOf<DispatchQueue> {
/// An immediate scheduler that can substitute itself for a dispatch queue.
public static var immediate: Self {
DispatchQueue.immediate.eraseToAnyScheduler()
}
}

extension AnyScheduler
where
SchedulerTimeType == OperationQueue.SchedulerTimeType,
SchedulerOptions == OperationQueue.SchedulerOptions
{
extension AnySchedulerOf<OperationQueue> {
/// An immediate scheduler that can substitute itself for an operation queue.
public static var immediate: Self {
OperationQueue.immediate.eraseToAnyScheduler()
}
}

extension AnyScheduler
where
SchedulerTimeType == RunLoop.SchedulerTimeType,
SchedulerOptions == RunLoop.SchedulerOptions
{
extension AnySchedulerOf<RunLoop> {
/// An immediate scheduler that can substitute itself for a run loop.
public static var immediate: Self {
RunLoop.immediate.eraseToAnyScheduler()
Expand Down
15 changes: 6 additions & 9 deletions Sources/CombineSchedulers/TestScheduler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@
/// but this technique can be used to test any publisher that involves Combine's asynchronous
/// operations.
///
public final class TestScheduler<SchedulerTimeType, SchedulerOptions>:
public final class TestScheduler<SchedulerTimeType: Strideable, SchedulerOptions>:
Scheduler, @unchecked Sendable
where SchedulerTimeType: Strideable, SchedulerTimeType.Stride: SchedulerTimeIntervalConvertible {
where SchedulerTimeType.Stride: SchedulerTimeIntervalConvertible {

private var lastSequence: UInt = 0
private let lock = NSRecursiveLock()
Expand Down Expand Up @@ -98,7 +98,6 @@
/// - Parameter duration: A stride. By default this argument is `.zero`, which does not advance
/// the scheduler's time but does cause the scheduler to execute any units of work that are
/// waiting to be performed for right now.
@MainActor
public func advance(by duration: SchedulerTimeType.Stride = .zero) async {
await self.advance(to: self.now.advanced(by: duration))
}
Expand Down Expand Up @@ -130,7 +129,6 @@
/// Advances the scheduler to the given instant.
///
/// - Parameter instant: An instant in time to advance to.
@MainActor
public func advance(to instant: SchedulerTimeType) async {
while self.lock.sync(operation: { self.now }) <= instant {
await Task.megaYield()
Expand Down Expand Up @@ -196,7 +194,6 @@
}
}

@MainActor
public func run() async {
await Task.megaYield()
while let date = self.lock.sync(operation: { self.scheduled.first?.date }) {
Expand Down Expand Up @@ -253,29 +250,29 @@
/// A test scheduler of dispatch queues.
public static var test: TestSchedulerOf<DispatchQueue> {
// NB: `DispatchTime(uptimeNanoseconds: 0) == .now())`. Use `1` for consistency.
.init(now: .init(.init(uptimeNanoseconds: 1)))
TestScheduler(now: DispatchQueue.SchedulerTimeType(DispatchTime(uptimeNanoseconds: 1)))
}
}

extension UIScheduler {
/// A test scheduler compatible with type erased UI schedulers.
public static var test: TestSchedulerOf<Self> {
// NB: `DispatchTime(uptimeNanoseconds: 0) == .now())`. Use `1` for consistency.
.init(now: .init(.init(uptimeNanoseconds: 1)))
TestScheduler(now: UIScheduler.SchedulerTimeType(DispatchTime(uptimeNanoseconds: 1)))
}
}

extension OperationQueue {
/// A test scheduler of operation queues.
public static var test: TestSchedulerOf<OperationQueue> {
.init(now: .init(.init(timeIntervalSince1970: 0)))
TestScheduler(now: OperationQueue.SchedulerTimeType(Date(timeIntervalSince1970: 0)))
}
}

extension RunLoop {
/// A test scheduler of run loops.
public static var test: TestSchedulerOf<RunLoop> {
.init(now: .init(.init(timeIntervalSince1970: 0)))
TestScheduler(now: RunLoop.SchedulerTimeType(Date(timeIntervalSince1970: 0)))
}
}

Expand Down
7 changes: 6 additions & 1 deletion Sources/CombineSchedulers/UIScheduler.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
#if canImport(Combine)
import Combine
import Dispatch

#if swift(>=6)
@preconcurrency import Dispatch
#else
import Dispatch
#endif

/// A scheduler that executes its work on the main queue as soon as possible.
///
Expand Down
Loading

0 comments on commit 36f9d82

Please sign in to comment.