From d97b621952cf13657833d4fbf4ed9f3b2e6dd0aa Mon Sep 17 00:00:00 2001 From: Scott Marchant Date: Tue, 6 May 2025 16:03:45 -0600 Subject: [PATCH 1/3] feat: Add support for wasm compilation. --- Package.resolved | 23 +++++++++++++ Package@swift-5.9.swift | 31 ++++++++++++++++- Platform/AtomicInt.swift | 33 +++++++++++++++++++ Platform/DispatchQueue+Extensions.swift | 4 +++ .../RxExample.xcdatamodeld/.xccurrentversion | 5 ++- RxSwift/Binder.swift | 23 +++++++++++++ RxSwift/Date+Dispatch.swift | 4 +++ RxSwift/ObservableType+Extensions.swift | 6 ++-- RxSwift/Observables/Create.swift | 4 +-- RxSwift/Observables/Delay.swift | 4 +++ RxSwift/Observables/Generate.swift | 2 ++ RxSwift/Observables/Merge.swift | 26 +++++++++++++++ RxSwift/Observables/ObserveOn.swift | 12 +++++-- RxSwift/Observables/Producer.swift | 9 +++++ RxSwift/Observables/Range.swift | 2 ++ RxSwift/Observables/Repeat.swift | 2 ++ RxSwift/Observables/Sequence.swift | 3 ++ RxSwift/Observables/Sink.swift | 4 +-- RxSwift/Observables/Throttle.swift | 3 ++ RxSwift/Reactive.swift | 4 +++ RxSwift/Rx.swift | 2 +- RxSwift/SchedulerType.swift | 4 ++- .../ConcurrentDispatchQueueScheduler.swift | 5 ++- .../Schedulers/ConcurrentMainScheduler.swift | 4 +++ .../Schedulers/CurrentThreadScheduler.swift | 4 +++ RxSwift/Schedulers/HistoricalScheduler.swift | 4 +++ .../Internal/DispatchQueueConfiguration.swift | 8 +++-- RxSwift/Schedulers/MainScheduler.swift | 3 ++ .../Schedulers/OperationQueueScheduler.swift | 4 +++ .../SerialDispatchQueueScheduler.swift | 18 ++++++++++ RxSwift/Schedulers/VirtualTimeScheduler.swift | 4 +++ RxSwift/Subjects/AsyncSubject.swift | 4 +-- RxSwift/Subjects/BehaviorSubject.swift | 4 +-- RxSwift/Subjects/PublishSubject.swift | 4 +-- RxSwift/Subjects/ReplaySubject.swift | 4 +-- .../Infallible/Infallible+Operators.swift | 6 ++++ .../PrimitiveSequence/Completable.swift | 2 +- RxSwift/Traits/PrimitiveSequence/Maybe.swift | 2 +- .../PrimitiveSequence/PrimitiveSequence.swift | 2 ++ RxSwift/Traits/PrimitiveSequence/Single.swift | 2 +- 40 files changed, 266 insertions(+), 28 deletions(-) create mode 100644 Package.resolved diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000000..535e8f406b --- /dev/null +++ b/Package.resolved @@ -0,0 +1,23 @@ +{ + "pins" : [ + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "cd142fd2f64be2100422d658e7411e39489da985", + "version" : "1.2.0" + } + }, + { + "identity" : "swift-dispatch-async", + "kind" : "remoteSourceControl", + "location" : "https://github.com/PassiveLogic/swift-dispatch-async.git", + "state" : { + "revision" : "d4251dd87be9ba2ad5d27328980e7165a392fd2f", + "version" : "0.0.1" + } + } + ], + "version" : 2 +} diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift index da26d14aba..6aa30dbcb5 100644 --- a/Package@swift-5.9.swift +++ b/Package@swift-5.9.swift @@ -68,9 +68,38 @@ let package = Package( ], Product.allTests() ] as [[Product]]).flatMap { $0 }, + dependencies: [ + // NOTE: Currently used for WASI targets only. + // Avoid using this dependency for any other targets. + .package( + url: "https://github.com/apple/swift-atomics.git", + from: "1.2.0" + ), + .package(url: "https://github.com/PassiveLogic/swift-dispatch-async.git", from: "0.0.1") + ], targets: ([ [ - .rxTarget(name: "RxSwift", dependencies: []), + .rxTarget( + name: "RxSwift", + dependencies: [ + // WASI targets can't use CoreFoundation, but WASI does support + // compiling Swift Atomics. + // + // This dependency is added ONLY for WASI targets, and should NOT + // be added for any other platforms. + .product( + name: "Atomics", + package: "swift-atomics", + condition: .when(platforms: [.wasi]) + ), + + .product( + name: "DispatchAsync", + package: "swift-dispatch-async", + condition: .when(platforms: [.wasi]) + ), + ] + ), ], Target.rxCocoa(), Target.rxCocoaRuntime(), diff --git a/Platform/AtomicInt.swift b/Platform/AtomicInt.swift index 303740f6f3..ea6ce4369d 100644 --- a/Platform/AtomicInt.swift +++ b/Platform/AtomicInt.swift @@ -6,6 +6,37 @@ // Copyright © 2018 Krunoslav Zaher. All rights reserved. // +#if canImport(Atomics) + +import Atomics + +typealias AtomicInt = ManagedAtomic + +@discardableResult +@inline(__always) +func add(_ this: AtomicInt, _ value: Int32) -> Int32 { + this.loadThenWrappingIncrement(by: value, ordering: .sequentiallyConsistent) +} + +@discardableResult +@inline(__always) +func sub(_ this: AtomicInt, _ value: Int32) -> Int32 { + this.loadThenWrappingDecrement(by: value, ordering: .sequentiallyConsistent) +} + +@discardableResult +@inline(__always) +func fetchOr(_ this: AtomicInt, _ mask: Int32) -> Int32 { + this.loadThenBitwiseOr(with: mask, ordering: .sequentiallyConsistent) +} + +@inline(__always) +func load(_ this: AtomicInt) -> Int32 { + this.load(ordering: .sequentiallyConsistent) +} + +#else + import CoreFoundation // This CoreFoundation import can be dropped when this issue is resolved: // https://github.com/swiftlang/swift-corelibs-foundation/pull/5122 @@ -56,6 +87,8 @@ func load(_ this: AtomicInt) -> Int32 { return oldValue } +#endif // end of #else condition + @discardableResult @inline(__always) func increment(_ this: AtomicInt) -> Int32 { diff --git a/Platform/DispatchQueue+Extensions.swift b/Platform/DispatchQueue+Extensions.swift index aaf24cae6e..13d36cc078 100644 --- a/Platform/DispatchQueue+Extensions.swift +++ b/Platform/DispatchQueue+Extensions.swift @@ -6,6 +6,8 @@ // Copyright © 2016 Krunoslav Zaher. All rights reserved. // +#if !os(WASI) + import Dispatch extension DispatchQueue { @@ -19,3 +21,5 @@ extension DispatchQueue { DispatchQueue.getSpecific(key: token) != nil } } + +#endif // !os(WASI) \ No newline at end of file diff --git a/RxExample/RxExample/RxExample.xcdatamodeld/.xccurrentversion b/RxExample/RxExample/RxExample.xcdatamodeld/.xccurrentversion index 0c67376eba..75802a0411 100644 --- a/RxExample/RxExample/RxExample.xcdatamodeld/.xccurrentversion +++ b/RxExample/RxExample/RxExample.xcdatamodeld/.xccurrentversion @@ -1,5 +1,8 @@ - + + _XCCurrentVersionName + RxExample.xcdatamodel + diff --git a/RxSwift/Binder.swift b/RxSwift/Binder.swift index 1aae09592a..3f5ae88c2b 100644 --- a/RxSwift/Binder.swift +++ b/RxSwift/Binder.swift @@ -25,6 +25,27 @@ public struct Binder: ObserverType { /// - parameter target: Target object. /// - parameter scheduler: Scheduler used to bind the events. /// - parameter binding: Binding logic. + #if os(WASI) + public init(_ target: Target, scheduler: ImmediateSchedulerType, binding: @escaping (Target, Value) -> Void) { + weak var weakTarget = target + + self.binding = { event in + switch event { + case .next(let element): + _ = scheduler.schedule(element) { element in + if let target = weakTarget { + binding(target, element) + } + return Disposables.create() + } + case .error(let error): + rxFatalErrorInDebug("Binding error: \(error)") + case .completed: + break + } + } + } + #else public init(_ target: Target, scheduler: ImmediateSchedulerType = MainScheduler(), binding: @escaping (Target, Value) -> Void) { weak var weakTarget = target @@ -44,6 +65,8 @@ public struct Binder: ObserverType { } } } + #endif + /// Binds next element to owner view as described in `binding`. public func on(_ event: Event) { diff --git a/RxSwift/Date+Dispatch.swift b/RxSwift/Date+Dispatch.swift index 90f70c560f..06da4f364d 100644 --- a/RxSwift/Date+Dispatch.swift +++ b/RxSwift/Date+Dispatch.swift @@ -6,6 +6,8 @@ // Copyright © 2019 Krunoslav Zaher. All rights reserved. // +#if !os(WASI) + import Dispatch import Foundation @@ -62,3 +64,5 @@ extension Date { } } + +#endif // !os(WASI) \ No newline at end of file diff --git a/RxSwift/ObservableType+Extensions.swift b/RxSwift/ObservableType+Extensions.swift index ac850c3ba7..f8493b960b 100644 --- a/RxSwift/ObservableType+Extensions.swift +++ b/RxSwift/ObservableType+Extensions.swift @@ -91,7 +91,7 @@ extension ObservableType { disposable = Disposables.create() } - #if DEBUG + #if DEBUG && !os(WASI) let synchronizationTracker = SynchronizationTracker() #endif @@ -99,7 +99,7 @@ extension ObservableType { let observer = AnonymousObserver { event in - #if DEBUG + #if DEBUG && !os(WASI) synchronizationTracker.register(synchronizationErrorMessage: .default) defer { synchronizationTracker.unregister() } #endif @@ -144,7 +144,7 @@ extension Hooks { #endif } private static var _customCaptureSubscriptionCallstack: CustomCaptureSubscriptionCallstack = { - #if DEBUG + #if DEBUG && !os(WASI) return Thread.callStackSymbols #else return [] diff --git a/RxSwift/Observables/Create.swift b/RxSwift/Observables/Create.swift index ca81442ce8..d4d14d89a6 100644 --- a/RxSwift/Observables/Create.swift +++ b/RxSwift/Observables/Create.swift @@ -29,7 +29,7 @@ final private class AnonymousObservableSink: Sink: Sink) { - #if DEBUG + #if DEBUG && !os(WASI) self.synchronizationTracker.register(synchronizationErrorMessage: .default) defer { self.synchronizationTracker.unregister() } #endif diff --git a/RxSwift/Observables/Delay.swift b/RxSwift/Observables/Delay.swift index 9f71c32d09..42c63cf9be 100644 --- a/RxSwift/Observables/Delay.swift +++ b/RxSwift/Observables/Delay.swift @@ -6,6 +6,8 @@ // Copyright © 2016 Krunoslav Zaher. All rights reserved. // +#if !os(WASI) + import Foundation extension ObservableType { @@ -172,3 +174,5 @@ final private class Delay: Producer { return (sink: sink, subscription: subscription) } } + +#endif // !os(WASI) \ No newline at end of file diff --git a/RxSwift/Observables/Generate.swift b/RxSwift/Observables/Generate.swift index 7b924b3812..576a1dc93c 100644 --- a/RxSwift/Observables/Generate.swift +++ b/RxSwift/Observables/Generate.swift @@ -6,6 +6,7 @@ // Copyright © 2015 Krunoslav Zaher. All rights reserved. // +#if !os(WASI) extension ObservableType { /** Generates an observable sequence by running a state-driven loop producing the sequence's elements, using the specified scheduler @@ -23,6 +24,7 @@ extension ObservableType { Generate(initialState: initialState, condition: condition, iterate: iterate, resultSelector: { $0 }, scheduler: scheduler) } } +#endif // !os(WASI) final private class GenerateSink: Sink { typealias Parent = Generate diff --git a/RxSwift/Observables/Merge.swift b/RxSwift/Observables/Merge.swift index c0c901a2fb..636fd6307c 100644 --- a/RxSwift/Observables/Merge.swift +++ b/RxSwift/Observables/Merge.swift @@ -53,6 +53,7 @@ extension ObservableType where Element: ObservableConvertibleType { Merge(source: self.asObservable()) } + #if !os(WASI) /** Merges elements from all inner observable sequences into a single observable sequence, limiting the number of concurrent subscriptions to inner sequences. @@ -65,8 +66,10 @@ extension ObservableType where Element: ObservableConvertibleType { -> Observable { MergeLimited(source: self.asObservable(), maxConcurrent: maxConcurrent) } + #endif // !os(WASI) } +#if !os(WASI) extension ObservableType where Element: ObservableConvertibleType { /** @@ -80,6 +83,7 @@ extension ObservableType where Element: ObservableConvertibleType { self.merge(maxConcurrent: 1) } } +#endif // !os(WASI) extension ObservableType { /** @@ -243,6 +247,27 @@ private class MergeLimitedSink Observable { + public func observe(on scheduler: ImmediateSchedulerType) -> Observable { + #if os(WASI) + return ObserveOn(source: self.asObservable(), scheduler: scheduler) + #else guard let serialScheduler = scheduler as? SerialDispatchQueueScheduler else { return ObserveOn(source: self.asObservable(), scheduler: scheduler) } return ObserveOnSerialDispatchQueue(source: self.asObservable(), - scheduler: serialScheduler) + scheduler: serialScheduler) + #endif } /** @@ -176,6 +179,8 @@ final private class ObserveOnSink: ObserverBase: ObserverBase { let scheduler: SerialDispatchQueueScheduler let observer: Observer @@ -241,3 +246,4 @@ final private class ObserveOnSerialDispatchQueue: Producer { } #endif } +#endif // !os(WASI) \ No newline at end of file diff --git a/RxSwift/Observables/Producer.swift b/RxSwift/Observables/Producer.swift index e611930a52..ed41512c2c 100644 --- a/RxSwift/Observables/Producer.swift +++ b/RxSwift/Observables/Producer.swift @@ -12,6 +12,14 @@ class Producer: Observable { } override func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Element { + #if os(WASI) // TODO: Is this simplification ok? Also, if CurrentThreadScheduler compiled, it would be better to use that. + // The returned disposable needs to release all references once it was disposed. + let disposer = SinkDisposer() + let sinkAndSubscription = self.run(observer, cancel: disposer) + disposer.setSinkAndSubscription(sink: sinkAndSubscription.sink, subscription: sinkAndSubscription.subscription) + + return disposer + #else if !CurrentThreadScheduler.isScheduleRequired { // The returned disposable needs to release all references once it was disposed. let disposer = SinkDisposer() @@ -29,6 +37,7 @@ class Producer: Observable { return disposer } } + #endif // !os(WASI) } func run(_ observer: Observer, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where Observer.Element == Element { diff --git a/RxSwift/Observables/Range.swift b/RxSwift/Observables/Range.swift index 2453532494..22f09e7847 100644 --- a/RxSwift/Observables/Range.swift +++ b/RxSwift/Observables/Range.swift @@ -6,6 +6,7 @@ // Copyright © 2015 Krunoslav Zaher. All rights reserved. // +#if !os(WASI) extension ObservableType where Element: RxAbstractInteger { /** Generates an observable sequence of integral numbers within a specified range, using the specified scheduler to generate and send out observer messages. @@ -21,6 +22,7 @@ extension ObservableType where Element: RxAbstractInteger { RangeProducer(start: start, count: count, scheduler: scheduler) } } +#endif // !os(WASI) final private class RangeProducer: Producer { fileprivate let start: Element diff --git a/RxSwift/Observables/Repeat.swift b/RxSwift/Observables/Repeat.swift index 69a3ba02e6..928e24534b 100644 --- a/RxSwift/Observables/Repeat.swift +++ b/RxSwift/Observables/Repeat.swift @@ -6,6 +6,7 @@ // Copyright © 2015 Krunoslav Zaher. All rights reserved. // +#if !os(WASI) extension ObservableType { /** Generates an observable sequence that repeats the given element infinitely, using the specified scheduler to send out observer messages. @@ -20,6 +21,7 @@ extension ObservableType { RepeatElement(element: element, scheduler: scheduler) } } +#endif // !os(WASI) final private class RepeatElement: Producer { fileprivate let element: Element diff --git a/RxSwift/Observables/Sequence.swift b/RxSwift/Observables/Sequence.swift index c6d10896e7..fa5d1738fd 100644 --- a/RxSwift/Observables/Sequence.swift +++ b/RxSwift/Observables/Sequence.swift @@ -6,6 +6,7 @@ // Copyright © 2015 Krunoslav Zaher. All rights reserved. // +#if !os(WASI) extension ObservableType { // MARK: of @@ -47,6 +48,8 @@ extension ObservableType { } } +#endif // !os(WASI) + final private class ObservableSequenceSink: Sink where Sequence.Element == Observer.Element { typealias Parent = ObservableSequence diff --git a/RxSwift/Observables/Sink.swift b/RxSwift/Observables/Sink.swift index ed4fec01e0..d7bccc2dd7 100644 --- a/RxSwift/Observables/Sink.swift +++ b/RxSwift/Observables/Sink.swift @@ -11,7 +11,7 @@ class Sink: Disposable { fileprivate let cancel: Cancelable private let disposed = AtomicInt(0) - #if DEBUG + #if DEBUG && !os(WASI) private let synchronizationTracker = SynchronizationTracker() #endif @@ -24,7 +24,7 @@ class Sink: Disposable { } final func forwardOn(_ event: Event) { - #if DEBUG + #if DEBUG && !os(WASI) self.synchronizationTracker.register(synchronizationErrorMessage: .default) defer { self.synchronizationTracker.unregister() } #endif diff --git a/RxSwift/Observables/Throttle.swift b/RxSwift/Observables/Throttle.swift index 86152a8372..a6858c56f4 100644 --- a/RxSwift/Observables/Throttle.swift +++ b/RxSwift/Observables/Throttle.swift @@ -6,6 +6,8 @@ // Copyright © 2015 Krunoslav Zaher. All rights reserved. // +#if !os(WASI) + import Foundation extension ObservableType { @@ -158,3 +160,4 @@ final private class Throttle: Producer { } } +#endif // !os(WASI) \ No newline at end of file diff --git a/RxSwift/Reactive.swift b/RxSwift/Reactive.swift index 1bf390dc5d..7fb0e9b739 100644 --- a/RxSwift/Reactive.swift +++ b/RxSwift/Reactive.swift @@ -6,6 +6,8 @@ // Copyright © 2016 Krunoslav Zaher. All rights reserved. // +#if !os(WASI) + /** Use `Reactive` proxy as customization point for constrained protocol extensions. @@ -78,3 +80,5 @@ import Foundation /// Extend NSObject with `rx` proxy. extension NSObject: ReactiveCompatible { } + +#endif // !os(WASI) \ No newline at end of file diff --git a/RxSwift/Rx.swift b/RxSwift/Rx.swift index bcdd27947c..bbc04bf5c1 100644 --- a/RxSwift/Rx.swift +++ b/RxSwift/Rx.swift @@ -65,7 +65,7 @@ func decrementChecked(_ i: inout Int) throws -> Int { return i } -#if DEBUG +#if DEBUG && !os(WASI) import Foundation final class SynchronizationTracker { private let lock = RecursiveLock() diff --git a/RxSwift/SchedulerType.swift b/RxSwift/SchedulerType.swift index 6c8fe4a38f..7561a160e3 100644 --- a/RxSwift/SchedulerType.swift +++ b/RxSwift/SchedulerType.swift @@ -6,7 +6,9 @@ // Copyright © 2015 Krunoslav Zaher. All rights reserved. // -import Dispatch +#if canImport(DispatchAsync) + import DispatchAsync +#endif import Foundation // Type that represents time interval in the context of RxSwift. diff --git a/RxSwift/Schedulers/ConcurrentDispatchQueueScheduler.swift b/RxSwift/Schedulers/ConcurrentDispatchQueueScheduler.swift index 02c47c9178..22f33f04db 100644 --- a/RxSwift/Schedulers/ConcurrentDispatchQueueScheduler.swift +++ b/RxSwift/Schedulers/ConcurrentDispatchQueueScheduler.swift @@ -6,7 +6,8 @@ // Copyright © 2015 Krunoslav Zaher. All rights reserved. // -import Dispatch +#if !os(WASI) + import Foundation /// Abstracts the work that needs to be performed on a specific `dispatch_queue_t`. You can also pass a serial dispatch queue, it shouldn't cause any problems. @@ -80,3 +81,5 @@ public class ConcurrentDispatchQueueScheduler: SchedulerType { self.configuration.schedulePeriodic(state, startAfter: startAfter, period: period, action: action) } } + +#endif // !os(WASI) diff --git a/RxSwift/Schedulers/ConcurrentMainScheduler.swift b/RxSwift/Schedulers/ConcurrentMainScheduler.swift index c438d7d4da..3b848c10c2 100644 --- a/RxSwift/Schedulers/ConcurrentMainScheduler.swift +++ b/RxSwift/Schedulers/ConcurrentMainScheduler.swift @@ -6,6 +6,8 @@ // Copyright © 2015 Krunoslav Zaher. All rights reserved. // +#if !os(WASI) + import Dispatch import Foundation @@ -85,3 +87,5 @@ public final class ConcurrentMainScheduler : SchedulerType { self.mainScheduler.schedulePeriodic(state, startAfter: startAfter, period: period, action: action) } } + +#endif // !os(WASI) diff --git a/RxSwift/Schedulers/CurrentThreadScheduler.swift b/RxSwift/Schedulers/CurrentThreadScheduler.swift index 16a7dd328e..2ad8c22b68 100644 --- a/RxSwift/Schedulers/CurrentThreadScheduler.swift +++ b/RxSwift/Schedulers/CurrentThreadScheduler.swift @@ -6,6 +6,8 @@ // Copyright © 2015 Krunoslav Zaher. All rights reserved. // +#if !os(WASI) + import Dispatch import Foundation @@ -129,3 +131,5 @@ public class CurrentThreadScheduler : ImmediateSchedulerType { return scheduledItem } } + +#endif // !os(WASI) \ No newline at end of file diff --git a/RxSwift/Schedulers/HistoricalScheduler.swift b/RxSwift/Schedulers/HistoricalScheduler.swift index 254a3e83ce..dfafc3a14c 100644 --- a/RxSwift/Schedulers/HistoricalScheduler.swift +++ b/RxSwift/Schedulers/HistoricalScheduler.swift @@ -6,6 +6,8 @@ // Copyright © 2015 Krunoslav Zaher. All rights reserved. // +#if !os(WASI) + import Foundation /// Provides a virtual time scheduler that uses `Date` for absolute time and `TimeInterval` for relative time. @@ -20,3 +22,5 @@ public class HistoricalScheduler : VirtualTimeScheduler(_ state: StateType, dueTime: RxTimeInterval, action: @escaping (StateType) -> Disposable) -> Disposable { let deadline = DispatchTime.now() + dueTime @@ -45,7 +48,7 @@ extension DispatchQueueConfiguration { // It looks like just setting timer to fire and not holding a reference to it // until deadline causes timer cancellation. var timerReference: DispatchSourceTimer? = timer - let cancelTimer = Disposables.create { + let cancelTimer: any Cancelable = Disposables.create { timerReference?.cancel() timerReference = nil } @@ -94,4 +97,5 @@ extension DispatchQueueConfiguration { return cancelTimer } + #endif // !os(WASI) } diff --git a/RxSwift/Schedulers/MainScheduler.swift b/RxSwift/Schedulers/MainScheduler.swift index f6a507f3f5..ebc2794937 100644 --- a/RxSwift/Schedulers/MainScheduler.swift +++ b/RxSwift/Schedulers/MainScheduler.swift @@ -6,6 +6,8 @@ // Copyright © 2015 Krunoslav Zaher. All rights reserved. // +#if !os(WASI) + import Dispatch #if !os(Linux) import Foundation @@ -78,3 +80,4 @@ public final class MainScheduler : SerialDispatchQueueScheduler { return cancel } } +#endif // !os(WASI) \ No newline at end of file diff --git a/RxSwift/Schedulers/OperationQueueScheduler.swift b/RxSwift/Schedulers/OperationQueueScheduler.swift index f0ad3d3ce5..728d3f2e70 100644 --- a/RxSwift/Schedulers/OperationQueueScheduler.swift +++ b/RxSwift/Schedulers/OperationQueueScheduler.swift @@ -6,6 +6,8 @@ // Copyright © 2015 Krunoslav Zaher. All rights reserved. // +#if !os(WASI) + import Dispatch import Foundation @@ -52,3 +54,5 @@ public class OperationQueueScheduler: ImmediateSchedulerType { } } + +#endif // !os(WASI) diff --git a/RxSwift/Schedulers/SerialDispatchQueueScheduler.swift b/RxSwift/Schedulers/SerialDispatchQueueScheduler.swift index bb0013c8f5..f1193cfefb 100644 --- a/RxSwift/Schedulers/SerialDispatchQueueScheduler.swift +++ b/RxSwift/Schedulers/SerialDispatchQueueScheduler.swift @@ -6,6 +6,8 @@ // Copyright © 2015 Krunoslav Zaher. All rights reserved. // +#if !os(WASI) + import Dispatch import Foundation @@ -77,6 +79,7 @@ public class SerialDispatchQueueScheduler : SchedulerType { self.init(serialQueue: serialQueue, leeway: leeway) } + #if os(WASI) && canImport(DispatchAsync) /** Constructs new `SerialDispatchQueueScheduler` that wraps one of the global concurrent dispatch queues. @@ -85,9 +88,22 @@ public class SerialDispatchQueueScheduler : SchedulerType { - parameter leeway: The amount of time, in nanoseconds, that the system will defer the timer. */ @available(macOS 10.10, *) + public convenience init(internalSerialQueueName: String = "rx.global_dispatch_queue.serial", leeway: DispatchTimeInterval = DispatchTimeInterval.nanoseconds(0)) { + self.init(queue: DispatchQueue.global(), internalSerialQueueName: internalSerialQueueName, leeway: leeway) + } + #else + /** + Constructs new `SerialDispatchQueueScheduler` that wraps one of the global concurrent dispatch queues. + + - parameter qos: Identifier for global dispatch queue with specified quality of service class. + - parameter internalSerialQueueName: Custom name for internal serial dispatch queue proxy. + - parameter leeway: The amount of time, in nanoseconds, that the system will defer the timer. + */ + @available(macOS 10.10, *) public convenience init(qos: DispatchQoS, internalSerialQueueName: String = "rx.global_dispatch_queue.serial", leeway: DispatchTimeInterval = DispatchTimeInterval.nanoseconds(0)) { self.init(queue: DispatchQueue.global(qos: qos.qosClass), internalSerialQueueName: internalSerialQueueName, leeway: leeway) } + #endif /** Schedules an action to be executed immediately. @@ -129,3 +145,5 @@ public class SerialDispatchQueueScheduler : SchedulerType { self.configuration.schedulePeriodic(state, startAfter: startAfter, period: period, action: action) } } + +#endif // !os(WASI) diff --git a/RxSwift/Schedulers/VirtualTimeScheduler.swift b/RxSwift/Schedulers/VirtualTimeScheduler.swift index 5d2d61e147..b9d95b78f6 100644 --- a/RxSwift/Schedulers/VirtualTimeScheduler.swift +++ b/RxSwift/Schedulers/VirtualTimeScheduler.swift @@ -6,6 +6,8 @@ // Copyright © 2015 Krunoslav Zaher. All rights reserved. // +#if !os(WASI) + import Foundation /// Base class for virtual time schedulers using a priority queue for scheduled items. @@ -271,3 +273,5 @@ extension VirtualSchedulerItem "\(self.time)" } } + +#endif // !os(WASI) \ No newline at end of file diff --git a/RxSwift/Subjects/AsyncSubject.swift b/RxSwift/Subjects/AsyncSubject.swift index 30b012f0bc..9cc8ed10a5 100644 --- a/RxSwift/Subjects/AsyncSubject.swift +++ b/RxSwift/Subjects/AsyncSubject.swift @@ -39,7 +39,7 @@ public final class AsyncSubject } private var lastElement: Element? - #if DEBUG + #if DEBUG && !os(WASI) private let synchronizationTracker = SynchronizationTracker() #endif @@ -56,7 +56,7 @@ public final class AsyncSubject /// /// - parameter event: Event to send to the observers. public func on(_ event: Event) { - #if DEBUG + #if DEBUG && !os(WASI) self.synchronizationTracker.register(synchronizationErrorMessage: .default) defer { self.synchronizationTracker.unregister() } #endif diff --git a/RxSwift/Subjects/BehaviorSubject.swift b/RxSwift/Subjects/BehaviorSubject.swift index 7251f616b9..2efa1fe0ac 100644 --- a/RxSwift/Subjects/BehaviorSubject.swift +++ b/RxSwift/Subjects/BehaviorSubject.swift @@ -33,7 +33,7 @@ public final class BehaviorSubject private var observers = Observers() private var stoppedEvent: Event? - #if DEBUG + #if DEBUG && !os(WASI) private let synchronizationTracker = SynchronizationTracker() #endif @@ -75,7 +75,7 @@ public final class BehaviorSubject /// /// - parameter event: Event to send to the observers. public func on(_ event: Event) { - #if DEBUG + #if DEBUG && !os(WASI) self.synchronizationTracker.register(synchronizationErrorMessage: .default) defer { self.synchronizationTracker.unregister() } #endif diff --git a/RxSwift/Subjects/PublishSubject.swift b/RxSwift/Subjects/PublishSubject.swift index 0318486ef9..0f492bdad4 100644 --- a/RxSwift/Subjects/PublishSubject.swift +++ b/RxSwift/Subjects/PublishSubject.swift @@ -33,7 +33,7 @@ public final class PublishSubject private var stopped = false private var stoppedEvent = nil as Event? - #if DEBUG + #if DEBUG && !os(WASI) private let synchronizationTracker = SynchronizationTracker() #endif @@ -54,7 +54,7 @@ public final class PublishSubject /// /// - parameter event: Event to send to the observers. public func on(_ event: Event) { - #if DEBUG + #if DEBUG && !os(WASI) self.synchronizationTracker.register(synchronizationErrorMessage: .default) defer { self.synchronizationTracker.unregister() } #endif diff --git a/RxSwift/Subjects/ReplaySubject.swift b/RxSwift/Subjects/ReplaySubject.swift index 2336ce78f9..f6709ae34b 100644 --- a/RxSwift/Subjects/ReplaySubject.swift +++ b/RxSwift/Subjects/ReplaySubject.swift @@ -36,7 +36,7 @@ public class ReplaySubject } fileprivate var observers = Observers() - #if DEBUG + #if DEBUG && !os(WASI) fileprivate let synchronizationTracker = SynchronizationTracker() #endif @@ -108,7 +108,7 @@ private class ReplayBufferBase } override func on(_ event: Event) { - #if DEBUG + #if DEBUG && !os(WASI) self.synchronizationTracker.register(synchronizationErrorMessage: .default) defer { self.synchronizationTracker.unregister() } #endif diff --git a/RxSwift/Traits/Infallible/Infallible+Operators.swift b/RxSwift/Traits/Infallible/Infallible+Operators.swift index c9209f8744..56242f0b69 100644 --- a/RxSwift/Traits/Infallible/Infallible+Operators.swift +++ b/RxSwift/Traits/Infallible/Infallible+Operators.swift @@ -72,6 +72,8 @@ extension InfallibleType { } } +#if !os(WASI) + // MARK: From & Of extension Infallible { @@ -113,6 +115,8 @@ extension Infallible { } } +#endif // !os(WASI) + // MARK: - Filter extension InfallibleType { /** @@ -244,6 +248,7 @@ extension InfallibleType { Infallible(asObservable().debounce(dueTime, scheduler: scheduler)) } + #if !os(WASI) /** Returns an Observable that emits the first and the latest item emitted by the source Observable during sequential time windows of a specified duration. @@ -260,6 +265,7 @@ extension InfallibleType { -> Infallible { Infallible(asObservable().throttle(dueTime, latest: latest, scheduler: scheduler)) } + #endif // !os(WASI) } // MARK: - FlatMap diff --git a/RxSwift/Traits/PrimitiveSequence/Completable.swift b/RxSwift/Traits/PrimitiveSequence/Completable.swift index 1bca0ffa4d..8e050d5809 100644 --- a/RxSwift/Traits/PrimitiveSequence/Completable.swift +++ b/RxSwift/Traits/PrimitiveSequence/Completable.swift @@ -117,7 +117,7 @@ extension PrimitiveSequenceType where Trait == CompletableTrait, Element == Swif public func subscribe(onCompleted: (() -> Void)? = nil, onError: ((Swift.Error) -> Void)? = nil, onDisposed: (() -> Void)? = nil) -> Disposable { - #if DEBUG + #if DEBUG && !os(WASI) let callStack = Hooks.recordCallStackOnError ? Thread.callStackSymbols : [] #else let callStack = [String]() diff --git a/RxSwift/Traits/PrimitiveSequence/Maybe.swift b/RxSwift/Traits/PrimitiveSequence/Maybe.swift index d73048fb3e..3646b06704 100644 --- a/RxSwift/Traits/PrimitiveSequence/Maybe.swift +++ b/RxSwift/Traits/PrimitiveSequence/Maybe.swift @@ -133,7 +133,7 @@ extension PrimitiveSequenceType where Trait == MaybeTrait { onError: ((Swift.Error) -> Void)? = nil, onCompleted: (() -> Void)? = nil, onDisposed: (() -> Void)? = nil) -> Disposable { - #if DEBUG + #if DEBUG && !os(WASI) let callStack = Hooks.recordCallStackOnError ? Thread.callStackSymbols : [] #else let callStack = [String]() diff --git a/RxSwift/Traits/PrimitiveSequence/PrimitiveSequence.swift b/RxSwift/Traits/PrimitiveSequence/PrimitiveSequence.swift index 04733d5a46..424f4197b6 100644 --- a/RxSwift/Traits/PrimitiveSequence/PrimitiveSequence.swift +++ b/RxSwift/Traits/PrimitiveSequence/PrimitiveSequence.swift @@ -65,6 +65,7 @@ extension PrimitiveSequence { }) } + #if !os(WASI) /** Returns an observable sequence by the source observable sequence shifted forward in time by a specified delay. Error events from the source observable sequence are not delayed. @@ -78,6 +79,7 @@ extension PrimitiveSequence { -> PrimitiveSequence { PrimitiveSequence(raw: self.primitiveSequence.source.delay(dueTime, scheduler: scheduler)) } + #endif // !os(WASI) /** Time shifts the observable sequence by delaying the subscription with the specified relative time duration, using the specified scheduler to run timers. diff --git a/RxSwift/Traits/PrimitiveSequence/Single.swift b/RxSwift/Traits/PrimitiveSequence/Single.swift index 79595d175c..ac17df15cf 100644 --- a/RxSwift/Traits/PrimitiveSequence/Single.swift +++ b/RxSwift/Traits/PrimitiveSequence/Single.swift @@ -129,7 +129,7 @@ extension PrimitiveSequenceType where Trait == SingleTrait { public func subscribe(onSuccess: ((Element) -> Void)? = nil, onFailure: ((Swift.Error) -> Void)? = nil, onDisposed: (() -> Void)? = nil) -> Disposable { - #if DEBUG + #if DEBUG && !os(WASI) let callStack = Hooks.recordCallStackOnError ? Thread.callStackSymbols : [] #else let callStack = [String]() From ef33890951c2513a08940ce6081c379e0548a7bb Mon Sep 17 00:00:00 2001 From: Scott Marchant Date: Tue, 16 Dec 2025 13:06:51 -0700 Subject: [PATCH 2/3] chore: whitespace cleanup --- RxSwift/Observables/ObserveOn.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RxSwift/Observables/ObserveOn.swift b/RxSwift/Observables/ObserveOn.swift index cb6843ce95..7ec131e464 100644 --- a/RxSwift/Observables/ObserveOn.swift +++ b/RxSwift/Observables/ObserveOn.swift @@ -27,7 +27,7 @@ extension ObservableType { } return ObserveOnSerialDispatchQueue(source: self.asObservable(), - scheduler: serialScheduler) + scheduler: serialScheduler) #endif } @@ -246,4 +246,4 @@ final private class ObserveOnSerialDispatchQueue: Producer { } #endif } -#endif // !os(WASI) \ No newline at end of file +#endif // !os(WASI) From a1fbe74ba965fdbcb09d6bcb7e638505659fe9f8 Mon Sep 17 00:00:00 2001 From: Scott Marchant Date: Tue, 16 Dec 2025 13:07:06 -0700 Subject: [PATCH 3/3] build: Use latest swift-dispatch-async --- Package@swift-5.9.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift index 6aa30dbcb5..5762f17549 100644 --- a/Package@swift-5.9.swift +++ b/Package@swift-5.9.swift @@ -75,7 +75,7 @@ let package = Package( url: "https://github.com/apple/swift-atomics.git", from: "1.2.0" ), - .package(url: "https://github.com/PassiveLogic/swift-dispatch-async.git", from: "0.0.1") + .package(url: "https://github.com/PassiveLogic/swift-dispatch-async.git", from: "1.0.0") ], targets: ([ [