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

Swift异步调用中如何传递参数?含CompletionHandler场景示例

处理Swift中动态参数的异步CompletionHandler调用

这是个很常见的异步请求场景问题——当用户频繁触发查询(比如搜索框输入、筛选切换),旧的异步请求可能还在后台跑,等它返回时会覆盖新请求的结果,导致UI显示错误数据。我给你几个实用的解决方案,从简单到进阶:

方案1:取消旧请求(推荐,如果你的客户端支持)

首先检查你使用的client是否支持请求取消(比如基于URLSessionDataTask或者第三方库提供的Cancellable协议)。如果支持,我们可以在发起新请求前取消掉未完成的旧请求,从根源上避免旧结果干扰。

修改DataManager

import Combine // 若用Combine的Cancellable,或根据你的客户端类型调整

class DataManager {
    private var currentFetchTask: Cancellable? // 保存当前请求任务
    
    func requestVideoData(query: QueryOn<VideoModel>, completion: @escaping (([VideoModel]?, UInt?, Error?) -> Void)) {
        // 先取消旧请求
        currentFetchTask?.cancel()
        
        // 发起新请求并保存任务引用
        currentFetchTask = client.fetchMappedEntries(matching: query) { [weak self] result in
            guard let self = self else { return }
            // 处理result,解析成videos、arrayLength
            switch result {
            case .success(let response):
                let videos = response.entries.map { VideoModel(from: $0) } // 假设你有转模型的逻辑
                let arrayLength = response.totalCount
                completion(videos, arrayLength, nil)
            case .failure(let error):
                completion(nil, nil, error)
            }
            // 请求完成后清空任务引用
            self.currentFetchTask = nil
        }
    }
}

ViewController中调用

每次用户操作(比如输入搜索关键词、切换筛选条件)时,直接调用requestVideoData即可,旧请求会被自动取消:

class VideoViewController: UIViewController {
    private let dataManager = DataManager()
    
    // 用户触发查询的方法(比如搜索框值变化、按钮点击)
    func userDidTriggerNewQuery(_ newQuery: QueryOn<VideoModel>) {
        dataManager.requestVideoData(query: newQuery) { [weak self] videos, count, error in
            guard let self = self else { return }
            // 这里处理UI更新,因为旧请求已经被取消,只会收到最新请求的结果
            if let videos = videos {
                self.updateUI(with: videos)
            } else if let error = error {
                self.showError(error)
            }
        }
    }
}

方案2:标记请求有效性(无法取消请求时使用)

如果你的client不支持请求取消,我们可以给每个请求添加唯一标识,在回调时检查这个标识是否是当前“有效”的请求,无效的话就忽略结果。

ViewController中实现

class VideoViewController: UIViewController {
    private let dataManager = DataManager()
    private var activeRequestUUID: UUID? // 记录当前有效的请求ID
    
    func userDidTriggerNewQuery(_ newQuery: QueryOn<VideoModel>) {
        let requestUUID = UUID()
        activeRequestUUID = requestUUID // 更新为当前请求的ID
        
        dataManager.requestVideoData(query: newQuery) { [weak self] videos, count, error in
            guard let self = self else { return }
            // 检查当前请求是否是有效的(和最新的UUID匹配)
            guard self.activeRequestUUID == requestUUID else {
                // 旧请求的结果,直接忽略
                return
            }
            
            // 处理有效结果的UI更新
            if let videos = videos {
                self.updateUI(with: videos)
            } else if let error = error {
                self.showError(error)
            }
        }
    }
}

这种方法的核心是:每次发起新请求时,更新activeRequestUUID,旧请求返回时,发现自己的UUID和当前的不匹配,就不执行UI更新逻辑。

方案3:改用Async/Await(Swift 5.5+,更简洁的现代方案)

如果你的项目支持Swift 5.5及以上,建议改用async/await语法,配合Task的取消机制,代码会更简洁易读。

修改DataManager为Async/Await版本

class DataManager {
    func requestVideoData(query: QueryOn<VideoModel>) async throws -> ([VideoModel], UInt) {
        let result = try await client.fetchMappedEntries(matching: query) // 假设客户端已适配async/await
        let videos = result.entries.map { VideoModel(from: $0) }
        let totalCount = result.totalCount
        return (videos, totalCount)
    }
}

ViewController中调用

class VideoViewController: UIViewController {
    private let dataManager = DataManager()
    private var currentFetchTask: Task<Void, Never>? // 保存当前任务
    
    func userDidTriggerNewQuery(_ newQuery: QueryOn<VideoModel>) {
        // 取消旧任务
        currentFetchTask?.cancel()
        
        // 发起新任务
        currentFetchTask = Task { [weak self] in
            guard let self = self else { return }
            do {
                let (videos, count) = try await self.dataManager.requestVideoData(query: newQuery)
                // 在主线程更新UI
                DispatchQueue.main.async {
                    self.updateUI(with: videos)
                }
            } catch {
                // 处理错误,注意如果是任务被取消,不需要显示错误
                guard !(error is CancellationError) else { return }
                DispatchQueue.main.async {
                    self.showError(error)
                }
            }
        }
    }
}

这种方式利用Swift原生的并发框架,取消任务和管理请求生命周期都更直观,代码也更简洁。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 07:26:53