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

基于免费NBA API的特定球队球员多页加载性能优化问询

优化NBA API球员加载性能的Swift方案

嘿,我看了你这段加载NBA球队球员的代码,核心问题就是串行请求所有分页导致的加载慢,再加上几个线程安全和回调逻辑的小坑。我整理了几个实用的优化点,直接给你改好的代码和思路:


核心问题分析

你现在的代码是用for循环一页一页发起请求,必须等前一页返回才会发下一页——35页的话,总耗时就是35倍单页请求时间,这肯定慢。另外还有两个隐患:

  • players数组在多个线程被修改,会有数据竞争的风险
  • 每次请求完成都调用completion,调用方会收到35次回调,这显然不符合预期

优化后的完整代码

我把所有优化点整合到了下面的代码里,每一步都加了注释:

func loadPlayers(for team: Team, completion: @escaping (_ errors: [Error]?, _ players: [Player]) -> ()) {
    let rest = RestManager()
    // 配置请求头
    for (key, value) in headers {
        rest.requestHttpHeaders.add(value: value, forKey: key)
    }
    
    // 先获取总页数:如果调用方没传pages,建议先请求第一页拿到meta里的total_pages
    // 这里我先默认用35,你可以改成动态获取的逻辑
    let totalPages = 35
    
    var players = [Player]()
    var errors = [Error]()
    let dispatchGroup = DispatchGroup()
    // 用并发队列处理解析和筛选,不占主队列
    let processingQueue = DispatchQueue(label: "com.nba.player.processing", attributes: .concurrent)
    // 用串行队列保证players和errors的线程安全
    let syncQueue = DispatchQueue(label: "com.nba.player.sync")
    
    // 并行发起所有分页请求
    for page in 1...totalPages {
        dispatchGroup.enter()
        
        rest.makeRequest(withEndPoint: "players?per_page=100&page=\(page)", withHttpMethod: .get) { result in
            // 不管成功失败,都标记任务完成
            defer { dispatchGroup.leave() }
            
            // 处理响应为空的情况
            guard let response = result.response else {
                if let error = result.error {
                    syncQueue.async { errors.append(error) }
                }
                return
            }
            
            // 处理非200状态码
            guard response.httpStatusCode == 200 else {
                if let error = result.error {
                    syncQueue.async { errors.append(error) }
                }
                return
            }
            
            // 处理数据为空的情况
            guard let data = result.data else {
                let emptyDataError = NSError(domain: "NBAAPI", code: -1, userInfo: [NSLocalizedDescriptionKey: "Page \(page) returned no data"])
                syncQueue.async { errors.append(emptyDataError) }
                return
            }
            
            // 后台解析JSON并筛选球员,不占用主队列
            processingQueue.async {
                let decoder = JSONDecoder()
                guard let playerResult = try? decoder.decode(PlayerData.self, from: data) else {
                    let decodeError = NSError(domain: "NBAAPI", code: -2, userInfo: [NSLocalizedDescriptionKey: "Failed to decode page \(page) data"])
                    syncQueue.async { errors.append(decodeError) }
                    return
                }
                
                // 筛选当前球队的球员
                let teamPlayers = playerResult.data.filter { $0.team?.abbreviation == team.abbreviation }
                
                // 线程安全地添加到结果数组
                syncQueue.async {
                    players.append(contentsOf: teamPlayers)
                }
            }
        }
    }
    
    // 所有请求完成后,在主队列回调结果
    dispatchGroup.notify(queue: .main) {
        let finalErrors = errors.isEmpty ? nil : errors
        completion(finalErrors, players)
    }
}

关键优化点说明

  1. 并行请求所有分页:用DispatchGroup管理所有请求,同时发起35页的请求,总耗时直接降到最慢的那一页的时间,这是提升速度的核心。
  2. 线程安全保障:用专门的串行队列syncQueue来修改players和errors数组,避免多线程同时修改导致的数据混乱。
  3. 统一回调时机:只有当所有请求都完成后,才会调用一次completion,符合调用方的预期。
  4. 后台处理解析和筛选:把JSON解析、球员筛选的逻辑放到后台队列,不占用主队列,避免卡UI。
  5. 完善的错误处理:收集所有失败页面的错误,而不是单个失败就终止整个流程,调用方可以知道哪些页面出了问题。

额外的进阶优化建议

  • 动态获取总页数:不要硬编码35页,先发起一次请求(第一页),解析meta.total_pages,这样API更新页数后代码也不用改。
  • 添加请求取消能力:给方法加一个返回值(比如[URLSessionTask]),或者用自定义的Cancelable类,方便用户离开页面时取消所有未完成的请求。
  • 本地缓存:把筛选后的球员数据缓存到本地(比如UserDefaults、CoreData),下次用户查看同一球队时直接读缓存,不用重复请求API。
  • 内存优化:如果担心3500条球员数据占内存,可以边解析边筛选,像代码里这样,不用把所有球员都存到内存里。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 06:32:43