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

URLRequest请求完成前阻塞UI 放入后台队列仍卡顿问题求解

问题核心原因

你现有代码的卡顿和无效请求问题来自3个核心错误:

  • 搜索回调运行在主线程,你在回调中用Data(contentsOf: url)同步加载所有海报,同步网络请求会直接阻塞主线程,导致UI完全无响应
  • 没有对旧搜索请求做取消逻辑,用户连续输入会产生大量无用请求,抢占网络和CPU资源,还可能导致旧请求结果覆盖新请求的问题
  • 外层包的后台队列调用store.searchMovies是完全无效的,URLSession.dataTask本身就是异步执行,不会阻塞当前队列,多余的GCD调用反而会增加不必要的线程调度开销
修复方案

按以下步骤修改即可解决问题:

  1. 给搜索接口增加旧请求取消逻辑,发起新搜索前终止上一次还在执行的请求
  2. 增加输入防抖,用户输入停止后间隔一段时间再发起搜索,避免频繁触发无效请求
  3. 把一次性加载所有海报的逻辑移除,改成UITableViewCell显示时异步加载对应海报,同时可增加缓存避免重复请求
  4. 所有UI相关操作确保放在主线程执行,非UI逻辑不要占用主线程资源
修改后代码示例

Store层搜索方法修改

首先在Store类中增加当前搜索任务的存储变量,用来取消旧请求:

private var currentSearchTask: URLSessionDataTask?

func searchMovies(query: String, completion: @escaping (Result<[Movie], Error>) -> Void){
    // 发起新请求前取消上一次未完成的搜索
    currentSearchTask?.cancel()
    
    let url = TMDB_API.movieSearchURL(query: query)
    let request = URLRequest(url: url)
    let task = URLSession.shared.dataTask(with: request) { [weak self] (data, response, error) in
        // 忽略被取消的请求
        if let error = error as? URLError, error.code == .cancelled {
            return
        }
        let result = self?.processMoviesRequest(data: data, error: error) ?? .failure(MovieError.requestCancelled)
        OperationQueue.main.addOperation {
            completion(result)
        }
    }
    currentSearchTask = task
    task.resume()
}

搜索栏回调修改

在ViewController中增加防抖定时器,移除同步加载海报逻辑:

// 声明防抖定时器变量
private var searchDebounceTimer: Timer?

func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
    // 取消之前的待执行搜索任务
    searchDebounceTimer?.invalidate()
    // 输入停止300ms后发起搜索,可根据业务调整间隔时长
    searchDebounceTimer = Timer.scheduledTimer(withTimeInterval: 0.3, repeats: false) { [weak self] _ in
        guard let self = self else { return }
        self.store.searchMovies(query: searchText) { moviesResult in
            switch moviesResult {
            case .success(let movies):
                // 直接更新数据源,不要在这里处理海报加载
                MovieResults.movieResults.allResults = movies
                // 批量更新标识字段,避免额外遍历开销
                MovieResults.movieResults.allResults.forEach { $0.isAddedToList = false }
                self.tableView.reloadData()
            case .failure(let error):
                // 忽略请求取消的报错
                if let error = error as? URLError, error.code == .cancelled {
                    return
                }
                print(error)
            }
        }
    }
}

UITableViewCell异步加载海报

在cellForRowAt方法中处理单张海报的异步加载,避免一次性加载所有图片:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "MovieCell", for: indexPath) as! MovieCell
    let movie = MovieResults.movieResults.allResults[indexPath.row]
    
    // 先设置默认占位图,避免cell复用显示旧图片
    cell.posterImageView.image = UIImage(data: genericPosterImageData)
    cell.titleLabel.text = movie.title
    cell.releaseDateLabel.text = movie.releaseDate
    
    guard let posterPath = movie.poster_path,
          let posterUrl = URL(string: "https://image.tmdb.org/t/p/w185/\(posterPath)") else {
        return cell
    }
    
    // 优先读取已缓存的海报数据,没有则发起网络请求
    if let cachedThumb = movie.thumb {
        cell.posterImageView.image = UIImage(data: cachedThumb)
        return cell
    }
    
    // 异步加载海报
    URLSession.shared.dataTask(with: posterUrl) { [weak cell, weak movie] data, _, error in
        guard let data = data, error == nil, let image = UIImage(data: data) else {
            return
        }
        DispatchQueue.main.async {
            cell?.posterImageView.image = image
            // 缓存海报数据,下次滑动到该位置不用重新请求
            movie?.thumb = data
        }
    }.resume()
    
    return cell
}
额外优化建议
  • 可以将海报请求的尺寸从original改为和cell匹配的小尺寸(比如示例中的w185),大幅提升加载速度、减少流量消耗
  • 如果不想自己处理图片缓存、cell复用等逻辑,可以直接使用成熟的第三方图片加载库处理
  • 全局数据源MovieResults.movieResults的读写操作最好增加线程保护,避免多线程同时读写导致的崩溃问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.08 12:06:05