M1处理器下异步测试时Publisher未触发完成事件问题
问题分析与解决
问题核心
有一个从文件系统异步加载数据的Combine Publisher,非async标记的测试能正常执行完成,但标记为async的测试会超时失败,且该问题仅在M1设备上必现,Intel设备无此异常。由于需要在测试读取逻辑前await数据保存到文件系统的操作,必须解决async测试下的调度冲突问题。
根因推测
问题出在waitForPublication的实现逻辑上——它依赖XCTestExpectation的阻塞式等待(waitForExpectations),而在async测试的异步上下文里,这种阻塞机制会和Combine的调度队列产生冲突:
- 非async测试中,测试线程以同步阻塞方式等待,Combine的后台任务能正常调度执行;
- async测试本身运行在XCTest的异步执行上下文,
waitForExpectations的阻塞会打乱线程调度优先级,导致Publisher的任务无法及时被执行,最终触发超时。M1的ARM架构线程调度模型对异步上下文隔离更严格,因此该冲突会被放大并稳定复现,而Intel x86架构的调度容忍度更高,不会表现出异常。
解决方案:用async/await适配Combine
将基于XCTestExpectation的阻塞等待逻辑,替换为Combine原生的async/await支持,避免线程调度冲突。
1. 实现async版本的Publisher等待方法
替换原有的waitForPublication,用Combine的async/await能力实现非阻塞等待:
extension Publisher where Failure == Error { func waitForPublication() async throws -> Output { return try await self.first() } }
2. 修改async测试代码
用async/await方式等待Publisher结果,不再依赖阻塞式的Expectation:
func testFixed_localSavedLocallyIsUsedWhenAvailable() async { let url = testSpecificStoreURL() let (publisher, _) = makeSUT(url: url) do { let expectedData = try await publisher.waitForPublication() XCTAssertEqual("Example data", String(data: expectedData, encoding: .utf8)) } catch { XCTFail("Publisher failed with error: \(error)") } }
3. 兼容非async测试(可选)
如果需要保留原有非async测试的逻辑,可以同时保留原waitForPublication方法,让两类测试分别使用对应的等待方式。
内容的提问来源于stack exchange,提问作者regina_fallangi
相关产品推荐
相关产品推荐

