pointfreeco / combine-schedulers Goto Github PK
View Code? Open in Web Editor NEW⏰ A few schedulers that make working with Combine more testable and more versatile.
Home Page: https://www.pointfree.co
License: MIT License
⏰ A few schedulers that make working with Combine more testable and more versatile.
Home Page: https://www.pointfree.co
License: MIT License
This is a fantastic library and aids testing of time based asynchrony really well. But in a way, it only simulates the happiest path due to the precision of the scheduler. As we all know, when a timer is set it will complete at a point of timer after the scheduled time, if only a fraction of a second after. In your videos on Clocks, you demonstrate how this drift can add relatively quickly.
Would it be possible to add a feature where we can test against these small inaccuracies? So after advancing the scheduler, rather than the schedulers' .now
property being set exactly to the next scheduled actions date, it could be set with a defined drift, maybe even the .minimumTolerance
. This way, if we need to, we can test against these small inaccuracies of timing that can add up after many events.
Describe the bug
This problem is only related to iOS 13 (on iOS 14 and 15 everything is working fine) and maybe it's a bug in Combine itself. When the crash is happening action
is still performed on the main thread (check screenshot section).
To Reproduce
Actually hard to reproduce, in the case of our project it happens in tree cases (maybe more but we just don't figure out every one of them yet):
outputVolume
: // ...
extension Publisher where Failure == Never {
@inlinable
public func sinkValues(_ valueReceiver: @escaping (Output) -> Void) -> AnyCancellable {
return sink(receiveValue: valueReceiver)
}
}
// ...
// this code is called from application(_:didFinishLaunchingWithOptions)
try? AVAudioSession.sharedInstance().setCategory(.playback)
AVAudioSession.sharedInstance().publisher(for: \.outputVolume)
.receive(on: UIScheduler.shared) // crash happening here
.sinkValues { _ in
EduDoVideoPlayerPreferenceProvider.global.isMuted = false
}
.store(in: &soundManagerSubscriptions)
private func observeState() {
$state
.receive(on: UIScheduler.shared) // crash happening here
.sinkValues { [weak self] state in self?.configureView(using: state) })
.store(in: &subscriptions)
}
.receive(on: UIScheduler.shared)
. This happens not every time, sometimes it works fine and sometimes it crashes.Can't share all code due to NDA.
In all cases, the error is the same.
Expected behavior
App is not crashing 😓.
Screenshots
Reversed stack trace screenshots.
Breakpoint prints the result of Thread.isMainThread
and when it crashes it's still the main thread.
Environment
Describe the bug
I cannot build the tests on an M1 macbook. It seems to work fine on intel machine.
The issue is that if I start with a clean project I get No such module CombineSchedulers
in IDE where the import CombineSchedulers
is and when building my tests - under Compile Swift Source Files (arm64)
I also get the error No such module CombineSchedulers
.
If I build my app target first, the errors in IDE disappears. But then I proceed to building my tests and I get No such module MYMODULE
under Compile Swift Source Files (arm64)
.
Expected behavior
Test target should build and run tests.
Environment
Describe the bug
Not a bug
Hi guys, I'm using CADisplayLink in my project and want to inject AnyScheduler to test the behavior. However, CADisplayLink takes in RunLoop instead of Scheduler as a parameter to add(to:,forMode:) method.
So I cannot use AnySchedulerOf that I injected.
Is there a way to convert RunLoop.main.eraseToAnyScheduler()
back to RunLoop
type? Is it possible?
// And/or enter code that reproduces the behavior here.
private let mainLoop: AnySchedulerOf<RunLoop>
let displayLink = CADisplayLink(target: self, selector: #selector(update))
displayLink.add(to: self.mainLoop, forMode: .default) // <- Error Above
Environment
Additional context
Add any more context about the problem here.
Describe the bug
I looks like there is an issue where a subscriber does not receive values when test scheduler is used in combination with subscribe(on:)
and receive(on:)
operators.
To Reproduce
@MainActor func test_stuck() async {
let subject = PassthroughSubject<Int, Never>()
let scheduler = DispatchQueue.test
var values: [Int] = []
let cancelable = subject
.subscribe(on: scheduler)
.receive(on: scheduler)
.map { $0 * $0 }
.receive(on: scheduler)
.sink { value in values.append(value) }
let valuesToSend = [0, 1, 2, 3, 4]
for value in valuesToSend {
subject.send(value)
}
await scheduler.run()
XCTAssertEqual(values, [0, 1, 4, 9, 16])
}
Expected behavior
The above test should pass
Screenshots
N/A
Environment
Describe the bug
I am trying to use ImmediateScheduler
to avoid using XCTWaiter
to wait for a given debounce
to finish. However, when I pass in a immediate scheduler to debounce()
the value assigned by the subscriber is not set before the assertion happens. It won't even work if I add _ = XCTWaiter.wait(for: [XCTestExpectation()], timeout: 5)
even though the debounce is only for 0.5 seconds.
The test succeeds if I use DispatchQueue.main
and wait for 0.5 seconds. It might just be me understanding the functionality of purposes for ImmediateScheduler
// I am passing in a scheduler variable normally instead of hardcoding, just for demonstration purposes.
usernameState
.debounce(for: 0.5, scheduler: DispatchQueue.immediateScheduler)
.receive(on: DispatchQueue.immediateScheduler)
.map { $0.message }
.assign(to: \.usernameError, on: self)
.store(in: &cancellables)
// Test:
func testUsernameErrorMessage() throws {
var usernameError = ""
viewModel.$usernameError
.sink(receiveValue: { usernameError = $0 })
.store(in: &cancellables)
// Empty state
viewModel.username = ""
XCTAssertEqual(usernameError, "my error message")
}
Expected behavior
I expected the publisher to return the value immediately instead of having to wait, however it won't even work with an ImmediateScheduler
if I add XCTWaiter
.
Environment
Describe the bug
cocoapods spec is missing when running pod install
[!] Unable to find a specification for `CombineSchedulers`
To Reproduce
run pod repo update
or pod install --repo-update
, then do a pod search CombineSchedulers
, you will see:
[!] Unable to find a pod with name, author, summary, or description matching `CombineSchedulers`
Expected behavior
CombineSchedulers
should be searchable in pod repo, and pod install
should not fail if include pod CombineSchedulers
.
Environment
I am using Xcode 14.1, minimum deployments iOS 15.5. added CombineSchedulers via SPM, and import CombineSchedulers in my swift file.
But when I am using Publishers.Timer(every: .second(1), scheduler: scheduler)
API, I am getting Xcode error saying Cannot find Publishers in scope.
Is there anything I missed from the documentation? Thanks in advance for your help.
Describe the bug
This is probably a Swift compiler limitation/bug, which can be easily overcome with a simple workaround - see below.
To Reproduce
To compile with library evolution, you use the following command line:
xcrun xcodebuild -workspace "./.swiftpm/xcode/package.xcworkspace" -scheme "combine-schedulers" -configuration Debug -destination "generic/platform=iOS Simulator" -sdk iphonesimulator BUILD_LIBRARY_FOR_DISTRIBUTION=YES SKIP_INSTALL=NO CLANG_ENABLE_CODE_COVERAGE=NO GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=NO SWIFT_SERIALIZE_DEBUGGING_OPTIONS=NO "OTHER_SWIFT_FLAGS=-Xfrontend -no-serialize-debugging-options"
You can then inspect the resulting .swiftinterface file by looking in the PRODUCTS folder, in CombineSchedulers.swiftmodule
There you will see files like arm64-apple-ios-simulator.swiftinterface or x86_64-apple-ios-simulator.swiftinterface
The gist of the problem is the following line (235) in the file Timer.swift:
public func receive<S: Subscriber>(subscriber: S)
where Failure == S.Failure, Output == S.Input {
The compiler gets confused because the generic parameter 'S' in the receive function has the same name as the generic parameter 'S' in
public final class Timer<S: Scheduler>: ConnectablePublisher
(see line 83)
The Swift compiler gets confused because inside the .swiftinterface file nested types get fully qualified, so for receive
you will get something like this:
final public func receive<S>(subscriber: S) where S : Combine.Subscriber, S.SchedulerTimeType == S.Input, S.Failure == Swift.Never
And this is definitely confusing for the compiler.
The way to fix it is to rename the parameter 'S' in the receive to something else, e.g. 'SC':
public func receive<SC: Subscriber>(subscriber: SC)
where Failure == SC.Failure, Output == SC.Input
In that case the resulting .swiftinterface will get this:
final public func receive<SC>(subscriber: SC) where SC : Combine.Subscriber, S.SchedulerTimeType == SC.Input, SC.Failure == Swift.Never
Since it's a very easy fix, I would greatly appreciate if it's done in a following minor/patch release.
Describe the bug
This has 0.1.0 of xctest-dynamic-overlay
as a dependency. Release builds can now fail with the following error
Cannot find 'XCTFail' in scope
The version for xctest-dynamic-overlay
should probably be updated to >= 0.2 since 0.1 doesn't define XCTFail
in release builds and FailableScheduler
now exists in release builds.
To Reproduce
Build the repo using version 0.1.0 of xctest-dynamic-overlay
. Easiest way I know to reproduce it is with the following with will use the version in the Package.resolved.
swift build --disable-automatic-resolution --configuration release
Expected behavior
It should build
Environment
Describe the bug
When adding a type erased dispatch queue to my composable architecture environment, I found a run time error that occurs when the queue is erased to AnyScheduler. The code builds successfully, but as soon as it tries to load the app, it crashes, pointing to the "eraseToAnyScheduler()" call. This also occurs if I try to type erase locally with a labeled queue.
To Reproduce
schedularTest.zip
public struct HomeEnvironment {
public var mainQueue: AnySchedulerOf<DispatchQueue>
public init(
mainQueue: AnySchedulerOf<DispatchQueue>
) {
self.mainQueue = mainQueue
}
}
@main
struct schedularTestApp: App {
var body: some Scene {
WindowGroup {
ContentView(
store: .init(
initialState: HomeState(),
reducer: homeReducer,
environment: HomeEnvironment(mainQueue: .main)
)
)
}
}
}
Expected behavior
Scheduler is successfully erased and the app doesn't crash 🥲
Environment
Additional context
This bug was initially found when the preview would crash with no feedback. I then built out a specific preview target to try and run on the simulator. This also crashed, but gave me a stack trace that pointed to the erasing of the scheduler. Because my project was pretty intense, I created a new small test project to see if it was my project or the framework (see zipped project). Even in just this small app that only imports the latest release of the composable architecture, the bug still persists. I then tested on a device, and the bug was not there. My device was running iOS 15.1. I downloaded the 15.0 simulators and those do not have this bug either. It only appears to occur with iOS 15.2.
Currently this can only be installed with SPM, it'd be nice if it could be installed with Carthage as well.
Would that be a PR you'd be willing to accept? I was thinking it could be done in a similar way to how swift-snapshot-testing does it (with xcodegen creating the project).
Describe the bug
Thank you for the concurrency work!
Hoever I got a compile error when try to use latest TCA package 0.39.0
, which depends on this package at version 0.7.0
.
AnyScheduler.swift:333:48: error build: Protocol type with type arguments can only be used as a generic constraint
To Reproduce
Download the package, and compile.
Expected behavior
It should compile.
Environment
Describe the bug
When running archive build on Xcode 13 beta 3, the build fail since it depends on Combine.framework built by different compiler version.
Failed to build module 'Combine'; this SDK is not supported by the compiler (the SDK is built with 'Apple Swift version 5.5 (swiftlang-1300.0.24.14 clang-1300.0.25.10)', while this compiler is 'Apple Swift version 5.5 (swiftlang-1300.0.24.13 clang-1300.0.25.10)'). Please select a toolchain which matches the SDK.
Environment
Why does the UIScheduler have public typealias SchedulerOptions = Never
? It does not seem to fit to an API with AnySchedulerOf<DispatchQueue>
because the SchedulerOptions
types do not match. Or am I supposed to use AnySchedulerOf<UIScheduler>
for the API type if I want to also enable use of test scheduler? But then I can not replace the UIScheduler with DispatchQueue.main
.
I'm just wondering this because in ReactiveSwift the UIScheduler conforms to their own Scheduler
protocol (immediate scheduling only) and DispatchQueue conforms to DateScheduler: Scheduler
protocol (can schedule to the future). So if the API defines var scheduler: Scheduler
both UIScheduler and DispatchQueue.main can be used.
I'm not even sure if there is any use case to actually replace UIScheduler
with plain DispatchQueue
. I was just used to writing this kind of API 😅
This is giving some warnings because of Swift 6-preparation, like
Cannot use conformance of 'String' to 'CVarArg' here; 'Foundation' was not imported by this file; this is an error in Swift 6
Seems like the package is pinned to 0.3.0, whereas the package is at 0.8.5, including those warning fixes.
Describe the bug
Only as a heads up, when trying to build documentation on Use Xcode14 beta 5 fails with not found symbol identifiers.
To Reproduce
Use Xcode14 beta 5 with a project that has the CombineSchedulers
dependency and run "Build Documentation" from the Product menu
Expected behavior
Documentation builds and DocC
is exportable
Environment
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.