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
相关产品推荐
相关产品推荐

