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

UITableViewCell内耗时任务执行及滚动暂停处理方案咨询

这个场景在iOS开发里太常见了——UITableView异步加载关联数据,还要处理滚动时的请求控制。我给你梳理一套可落地的方案,从架构到代码实现都讲清楚:

一、核心原则:别直接在Cell里发起网络请求

首先得明确:Cell是可复用组件,直接在Cell内发起请求会导致三大问题:

  • 滚动时Cell被复用,同一个事件可能被重复请求
  • 无法统一控制所有请求的暂停/恢复
  • 容易出现内存泄漏或UI错乱

正确的做法是把网络请求逻辑抽离到专门的管理器或ViewModel中,Cell只负责展示数据。

二、实现滚动暂停/恢复请求的完整方案

1. 监听TableView的滚动状态

在你的事件列表ViewController里,通过UIScrollViewDelegate监听滚动状态,标记当前是否处于滚动中:

class EventListViewController: UIViewController, UITableViewDelegate {
    var isScrolling = false
    
    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        isScrolling = true
        // 通知请求管理器暂停非紧急请求
        RequestManager.shared.pausePendingRequests()
    }
    
    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if !decelerate {
            isScrolling = false
            RequestManager.shared.resumePendingRequests()
        }
    }
    
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        isScrolling = false
        RequestManager.shared.resumePendingRequests()
    }
}

2. 用单例请求管理器统一管控好友请求

创建一个RequestManager单例,负责管理所有好友列表请求,支持暂停、恢复、取消重复请求:

class RequestManager {
    static let shared = RequestManager()
    private var pendingTasks: [String: URLSessionDataTask] = [:] // key: eventId
    private let maxConcurrentRequests = 3 // 控制并发数,避免请求过载
    
    private init() {}
    
    func fetchFriends(for eventId: String, completion: @escaping ([Friend]) -> Void) {
        // 避免重复请求同一个事件的好友列表
        if let existingTask = pendingTasks[eventId] {
            if existingTask.state == .suspended {
                existingTask.resume()
            }
            return
        }
        
        guard let url = URL(string: "your_api_base_url/events/\(eventId)/friends") else {
            completion([])
            return
        }
        
        let task = URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
            guard let self = self, let data = data, error == nil else {
                completion([])
                self?.pendingTasks.removeValue(forKey: eventId)
                return
            }
            
            // 解析好友数据(这里假设你有Friend模型)
            let friends = try? JSONDecoder().decode([Friend].self, from: data)
            completion(friends ?? [])
            self?.pendingTasks.removeValue(forKey: eventId)
        }
        
        pendingTasks[eventId] = task
        // 非滚动状态下直接执行请求,否则先暂停
        if !EventListViewController.shared.isScrolling {
            task.resume()
        }
    }
    
    func pausePendingRequests() {
        pendingTasks.values.forEach { $0.suspend() }
    }
    
    func resumePendingRequests() {
        pendingTasks.values.forEach { $0.resume() }
    }
    
    func cancelRequest(for eventId: String) {
        pendingTasks[eventId]?.cancel()
        pendingTasks.removeValue(forKey: eventId)
    }
}

3. 用ViewModel连接Cell和请求逻辑

给每个事件创建对应的EventViewModel,负责触发请求、管理数据,并通知Cell更新UI:

class EventViewModel {
    let event: Event
    var friends: [Friend] = []
    var isLoadingFriends = false
    var onFriendsUpdated: (() -> Void)?
    
    init(event: Event) {
        self.event = event
    }
    
    func loadFriends() {
        guard !isLoadingFriends else { return }
        isLoadingFriends = true
        RequestManager.shared.fetchFriends(for: event.id) { [weak self] friends in
            self?.friends = friends
            self?.isLoadingFriends = false
            self?.onFriendsUpdated?()
        }
    }
    
    func cancelFriendLoad() {
        RequestManager.shared.cancelRequest(for: event.id)
        isLoadingFriends = false
    }
}

4. Cell中绑定ViewModel,处理复用逻辑

在EventCell中,通过ViewModel获取数据,同时在复用前取消当前请求、清空UI:

class EventCell: UITableViewCell {
    // 假设你有这些UI组件
    @IBOutlet weak var eventTitleLabel: UILabel!
    @IBOutlet weak var friendsPhotoContainer: UIView!
    @IBOutlet weak var loadingIndicator: UIActivityIndicatorView!
    
    var viewModel: EventViewModel? {
        didSet {
            guard let viewModel = viewModel else { return }
            // 先展示事件基础信息
            eventTitleLabel.text = viewModel.event.title
            
            // 展示好友数据或触发加载
            if !viewModel.friends.isEmpty {
                displayFriends(viewModel.friends)
                hideLoadingState()
            } else {
                showLoadingState()
                viewModel.onFriendsUpdated = { [weak self] in
                    DispatchQueue.main.async {
                        self?.displayFriends(viewModel.friends)
                        self?.hideLoadingState()
                    }
                }
                viewModel.loadFriends()
            }
        }
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        // 复用前清理资源
        viewModel?.cancelFriendLoad()
        viewModel = nil
        clearFriendsDisplay()
        hideLoadingState()
    }
    
    // 以下是UI辅助方法
    private func displayFriends(_ friends: [Friend]) {
        // 这里实现好友照片的展示逻辑,比如添加UIImageView到container
        // 建议用图片缓存库(如Kingfisher)加载好友头像
    }
    
    private func showLoadingState() {
        loadingIndicator.startAnimating()
        loadingIndicator.isHidden = false
    }
    
    private func hideLoadingState() {
        loadingIndicator.stopAnimating()
        loadingIndicator.isHidden = true
    }
    
    private func clearFriendsDisplay() {
        friendsPhotoContainer.subviews.forEach { $0.removeFromSuperview() }
    }
}
三、额外优化建议
  • 图片缓存:好友头像一定要用成熟的图片缓存库(Kingfisher/SDWebImage),避免重复下载,提升滚动流畅度
  • 预加载:在tableView(_:willDisplay:forRowAt:)方法中提前触发ViewModel的好友加载,让数据在Cell进入屏幕前就准备好
  • 请求优先级:滚动时可以将请求的qualityOfService设为.background,停止滚动后恢复.userInitiated,优化资源分配

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 10:35:11