Using the new Combine integration in 15.0.0-alpha.1, there is a crash in requestPublisher when using local stubs using Xcode 11.6, iOS 13.6 simulator.
``* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
frame #0: 0x00007fff235a9aca CombineCombine.Publishers.Catch.(Inner in _4B811D2092663748FE3021A2BDB676AF).receivePre(A.Output) -> Combine.Subscribers.Demand + 186
frame #1: 0x00007fff235a99ff CombineCombine.Publishers.Catch.(Inner in _4B811D2092663748FE3021A2BDB676AF).UncaughtS.receive(A.Output) -> Combine.Subscribers.Demand + 15
frame #2: 0x00007fff235ab567 Combinemerged protocol witness for Combine.Subscriber.receive(A.Input) -> Combine.Subscribers.Demand in conformance Combine.Publishers.Catch.(Inner in _4B811D2092663748FE3021A2BDB676AF)
frame #3: 0x00007fff23510703 CombineCombine.AnySubscriberBox.receive(A.Input) -> Combine.Subscribers.Demand + 35
frame #4: 0x00007fff23511982 CombineCombine.AnySubscriber.receive(A) -> Combine.Subscribers.Demand + 18
I'm not familiar enough with Combine to speculate about the cause here. According to the frame, `subscriber` has the value:
(AnySubscriber
box = 0x00007b0800066880 {}
descriptionThunk = 0x00007fff23511e20 Combinepartial apply forwarder for closure #1 () -> Swift.String in Combine.AnySubscriber.init<A where A == A1.Input, B == A1.Failure, A1: Combine.Subscriber>(A1) -> Combine.AnySubscriber<A, B>
customMirrorThunk = 0x00007fff23511d20 Combinepartial apply forwarder for closure #3 () -> Swift.Mirror in Combine.AnySubscriber.init(A1) -> Combine.AnySubscriber
playgroundDescriptionThunk = 0x00007fff23511de0 Combine`partial apply forwarder for closure #4 () -> Any in Combine.AnySubscriber.init(A1) -> Combine.AnySubscriber
combineIdentifier = {}
}
My testing code looks something like this:
```swift
func testThatValueCanBeFetchedAndObservedThroughValueProperty() {
// Given
let api = API(endpointClosure: API.all,
stubClosure: MoyaProvider.immediatelyStub)
let expect = expectation(description: "value should be fetched")
var value: Value?
// When
store {
api.$value
.compactMap { $0 }
.sink { value = $0; expect.fulfill() }
}
api.refresh()
waitForExpectations(timeout: timeout)
// Then
XCTAssertNotNil(value)
}
Where API has @Published properties which receive the output of the Moya network calls and refresh() calls a common function.
Using the app in the simulator works just fine, so this issue seems to be limited to testing.
Also getting the same crash in the .failure case, but getting more info in that case:
Thread 1: Fatal error: Unexpected state: received completion but do not have subscription
We're investigating whether this may be sim related.
Further investigation shows that this crash is likely due to a race between the stubbed response and the demand from the subscription being fully processed. Since MoyaPublisher.Subscription calls callback immediately, the request is finished before the subscription is fully received.
Yeah, it's definitely the race, as replacing immediatelyStub with delayedStub(0.1) fixes the issue. If I have time I'll look into a proper fix PR, but I need to finish this work first.
delayedStub(0) works as well, so really we just need the run loop to progress so the subscription is properly processed. Even though MoyaPublisher doesn't care about demand, it should wait until a demand is issued before executing the callback closure.
Most helpful comment
Yeah, it's definitely the race, as replacing
immediatelyStubwithdelayedStub(0.1)fixes the issue. If I have time I'll look into a proper fix PR, but I need to finish this work first.