如何自己实现 dequeueReusableCell?
没错,其实是面试遇到了2次问这个code题目。
1. 为什么要自己实现?
| 场景 | 原因 |
|---|---|
| 学习 & 复盘 | 理解系统内部实现,检验自己的想法 |
| 跨框架共享重用池 | 例如在 UICollectionView 与 UITableView 之间共用同一池 |
| 自定义生命周期 | 在 cell 被回收前做额外的清理或统计 |
需要提醒的是:生产环境仍建议使用官方的
dequeueReusableCell。下面实现仅作学习演示,性能和线程安全等细节并未做完整处理。
2. 核心思路
-
注册阶段
- 给每个
reuseIdentifier绑定一个 cell 类(或 nib)。 - 可以在字典中存储
String → UITableViewCell.Type。
- 给每个
-
重用池
- 维护一个
reuseIdentifier → [UITableViewCell]的字典。 - 当请求 cell 时,先从池里取;若为空则根据注册的类创建新实例。
- 维护一个
-
回收阶段
- 当 cell 失去可见性时,将其放回对应的池中。
- 在真实
UITableView中,系统会在didEndDisplayingCell回调里完成此操作;我们可以手动调用。
注意:在真正的
UITableView里,系统会在内部维护一个 reuse queue(基于链表)并且在多线程下保持安全。我们这里用数组模拟,单线程使用即可。
3. 代码实现
下面先给出 最简版 的实现(不考虑 nib、多线程、可变高度等复杂情况)。
3.1 最简版:自定义 ReusableTableView
import UIKit
/// 一个简单的 UITableView 子类,内部实现 cell 重用池
class ReusableTableView: UITableView {
// MARK: - 内部存储
/// reuseIdentifier → cell 类映射
private var registeredCellClasses: [String: UITableViewCell.Type] = [:]
/// reuseIdentifier → 可复用 cell 队列
private var reusePool: [String: [UITableViewCell]] = [:]
// MARK: - 注册
/// 注册 cell 类
func register(_ cellClass: UITableViewCell.Type, forCellReuseIdentifier identifier: String) {
registeredCellClasses[identifier] = cellClass
}
// MARK: - 重用
/// 取出可复用 cell
func myDequeueReusableCell(withIdentifier identifier: String) -> UITableViewCell? {
// 1️⃣ 尝试从池里取
if var queue = reusePool[identifier], !queue.isEmpty {
let cell = queue.removeFirst()
reusePool[identifier] = queue
return cell
}
// 2️⃣ 没有可用的,创建一个新的
guard let cellClass = registeredCellClasses[identifier] else {
// 未注册
return nil
}
let cell = cellClass.init(style: .default, reuseIdentifier: identifier)
return cell
}
// MARK: - 回收
/// 将不再可见的 cell 放回池中
func myRecycleCell(_ cell: UITableViewCell) {
guard let identifier = cell.reuseIdentifier else { return }
var queue = reusePool[identifier] ?? []
queue.append(cell)
reusePool[identifier] = queue
}
}