You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中如何实现串行多网络请求及数据签名流程?

问题分析与解决方案

你的核心需求是串行执行多轮完整请求流程(请求→WebView签名→二次提交),现有代码存在三个关键问题:

  1. 默认flatMap会并行发起请求,违反服务器串行处理的限制
  2. 未将「WebView签名」和「二次提交请求」整合到Combine流程中
  3. 用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 17:43:14