SwiftUI中如何实现串行多网络请求及数据签名流程?
问题分析与解决方案
你的核心需求是串行执行多轮完整请求流程(请求→WebView签名→二次提交),现有代码存在三个关键问题:
- 默认
flatMap会并行发起请求,违反服务器串行处理的限制 - 未将「WebView签名」和「二次提交请求」整合到Combine流程中
- 用
Result包裹返回值反而增加了Combine错误处理的复杂度
步骤1:将WebView签名逻辑封装为Combine Publisher
WebView的签名是UI触发的异步操作,需要通过PassthroughSubject把签名结果接入Combine流程:
// 在WebView对应的ViewModel中定义Subject,传递签名结果 class SigningViewModel: ObservableObject { let signCompleted = PassthroughSubject<String, Error>() @Published var dataToSign: String? // 触发WebView执行JS签名的方法 func startSigning(with rawData: String) { dataToSign = rawData // 当WebView中JS完成签名后,调用以下代码传递结果: // signCompleted.send(已签名的字符串) // 如果签名失败,调用: // signCompleted.send(completion: .failure(SigningError.signFailed)) } } // 自定义签名错误类型 enum SigningError: Error { case signFailed }
步骤2:简化请求方法的返回类型
去掉Result包裹,让Combine原生处理错误流,代码更简洁直观:
func makeSingleRequest(url: String) -> AnyPublisher<SomeObject, Error> { guard let url = URL(string: url) else { return Fail(error: URLError(.badURL)).eraseToAnyPublisher() } let request = createRequest() return URLSession.shared .dataTaskPublisher(for: request) .map(\.data) .tryMap { data in let response = try JSONDecoder().decode(SomeObject.self, from: data) guard response.error.isEmpty else { throw RequestError.serverError(response.error) } return response } .eraseToAnyPublisher() } // 自定义请求错误类型 enum RequestError: Error { case serverError(String) }
步骤3:构建串行的完整请求链路
用flatMap(maxPublishers: .max(1))强制串行执行,同时串联「请求→签名→二次提交」的全流程:
func makeMultiRequest(url: String, signingVM: SigningViewModel, completion: @escaping (Error?) -> Void) { Publishers.Sequence(sequence: Array(0..<3)) .flatMap(maxPublishers: .max(1)) { _ in // 1. 发起第一次请求,获取待签名数据 self.makeSingleRequest(url: url) .flatMap { response -> AnyPublisher<String, Error> in // 2. 触发WebView签名,返回签名结果的Publisher signingVM.startSigning(with: response.dataToSign) return signingVM.signCompleted.eraseToAnyPublisher() } .flatMap { signedData -> AnyPublisher<Void, Error> in // 3. 发起第二次请求,提交签名后的数据 return self.submitSignedData(signedData: signedData) } } .sink(receiveCompletion: { result in switch result { case .finished: completion(nil) case .failure(let error): completion(error) } }, receiveValue: {}) .store(in: &cancellables) } // 新增:提交签名后数据的请求方法 func submitSignedData(signedData: String) -> AnyPublisher<Void, Error> { guard let submitUrl = URL(string: "你的提交接口URL") else { return Fail(error: URLError(.badURL)).eraseToAnyPublisher() } var request = URLRequest(url: submitUrl) request.httpMethod = "POST" request.httpBody = signedData.data(using: .utf8) return URLSession.shared .dataTaskPublisher(for: request) .map { _ in () } // 仅关注请求完成,不处理返回数据 .mapError { $0 as Error } .eraseToAnyPublisher() }
关键改动说明
- 串行控制:
flatMap(maxPublishers: .max(1))确保每一轮完整流程(请求→签名→提交)执行完毕后,才会启动下一轮 - WebView与Combine衔接:通过
PassthroughSubject把WebView的异步签名结果转换成Combine可处理的数据流,解决UI操作与业务流程的联动问题 - 错误流简化:去掉
Result包裹后,Combine会自动传递错误,无需手动处理分支逻辑,代码可读性和可维护性更强
内容的提问来源于stack exchange,提问作者r360
相关产品推荐
相关产品推荐

