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

基于尾递归与Observable实现API多批次小文件加载的技术问询

使用RxSwift实现递归式批量文件加载

我来帮你把这个分批次递归加载小文件的思路落地成可运行的RxSwift代码,顺便拆解下每个操作符的核心作用,适配你提到的「单文件API+多并发请求+批次递归」的场景。

核心实现思路

你的思路完全没问题:用combineLatest合并同一批次的所有请求,确保整批完成后再推进到下一批;用flatMap+concat串联递归调用,保证批次之间是顺序执行的(不会同时发起多批请求,避免触发API限流)。

完整可运行代码示例

import RxSwift

// 模拟单个文件的API请求(替换成你的实际网络请求逻辑)
func loadSingleFile(_ fileId: String) -> Observable<Data> {
    return Observable.create { observer in
        // 这里可以替换为URLSession.rx.data(request:)等实际请求
        print("正在加载文件: \(fileId)")
        // 模拟请求成功返回数据
        observer.onNext(Data(fileId.utf8))
        observer.onCompleted()
        return Disposables.create()
    }
}

/// 递归式分批次加载文件
/// - Parameters:
///   - remainingFiles: 待加载的文件ID列表
///   - batchSize: 每批并发请求的数量(根据API限流调整)
/// - Returns: 按批次返回的文件数据序列
func loadFilesRecursively(remainingFiles: [String], batchSize: Int = 5) -> Observable<[Data]> {
    // 递归终止条件:没有剩余文件时,返回完成信号结束递归
    guard !remainingFiles.isEmpty else {
        return Observable.empty()
    }
    
    // 拆分当前批次和剩余待加载文件
    let currentBatch = Array(remainingFiles.prefix(batchSize))
    let nextBatchFiles = Array(remainingFiles.suffix(from: batchSize))
    
    // 生成当前批次所有文件的请求Observable
    let batchRequests = currentBatch.map(loadSingleFile)
    
    // 合并当前批次请求,完成后递归加载下一批
    return Observable.combineLatest(batchRequests)
        .flatMap { currentBatchResults -> Observable<[Data]> in
            // 先发送当前批次的加载结果
            let currentResult = Observable.just(currentBatchResults)
            // 递归加载下一批,用concat保证批次顺序执行(上一批完成才会启动下一批)
            let nextBatchResult = self.loadFilesRecursively(remainingFiles: nextBatchFiles, batchSize: batchSize)
            // 串联当前批次和下一批的结果序列
            return currentResult.concat(nextBatchResult)
        }
}

// 使用示例
let disposeBag = DisposeBag()
let allFileIds = ["file_01", "file_02", "file_03", "file_04", "file_05", "file_06", "file_07", "file_08"]

loadFilesRecursively(remainingFiles: allFileIds, batchSize: 3)
    .subscribe(
        onNext: { batchData in
            print("✅ 批次加载完成,共收到\(batchData.count)个文件数据")
        },
        onCompleted: {
            print("🎉 所有文件加载完成!")
        },
        onError: { error in
            print("❌ 加载出错:\(error.localizedDescription)")
        }
    )
    .disposed(by: disposeBag)

关键细节拆解

  • 批次并发控制:combineLatest会等待当前批次的所有请求都完成后,才会发射一个包含所有结果的数组,确保整批处理的原子性。
  • 递归衔接逻辑:flatMap里用concat串联当前批次结果和递归调用,concat会严格保证前一个Observable完成后,才会订阅下一个,完美实现「一批完成再启动下一批」的需求。
  • 终止条件:必须在remainingFiles为空时返回Observable.empty(),否则递归会无限循环。
  • 错误处理:如果某个文件请求失败,默认会终止整个序列。你可以给loadSingleFile添加catch/retry操作符来容错,比如:
    func loadSingleFile(_ fileId: String) -> Observable<Data> {
        return Observable.create { observer in
            // 模拟请求失败
            observer.onError(NSError(domain: "FileLoadError", code: -1, userInfo: [NSLocalizedDescriptionKey: "加载失败"]))
            return Disposables.create()
        }
        .retry(2) // 失败重试2次
        .catch { error in
            print("文件\(fileId)最终加载失败:\(error)")
            return Observable.just(Data()) // 返回空数据兜底,不终止整个批次
        }
    }
    

注意事项

  • 调整batchSize:根据API的并发限制设置合理的批次大小,避免触发限流。
  • 内存管理:确保用DisposeBag管理订阅,防止内存泄漏。
  • 进度追踪:如果需要展示加载进度,可以在onNext回调里累计已加载的文件数量。

内容的提问来源于stack exchange,提问作者beseder

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 03:39:06