如何自己实现 dequeueReusableCell?

没错,其实是面试遇到了2次问这个code题目。


1. 为什么要自己实现?

场景 原因
学习 & 复盘 理解系统内部实现,检验自己的想法
跨框架共享重用池 例如在 UICollectionViewUITableView 之间共用同一池
自定义生命周期 在 cell 被回收前做额外的清理或统计

需要提醒的是:生产环境仍建议使用官方的 dequeueReusableCell。下面实现仅作学习演示,性能和线程安全等细节并未做完整处理。


2. 核心思路

  1. 注册阶段

    • 给每个 reuseIdentifier 绑定一个 cell 类(或 nib)。
    • 可以在字典中存储 String → UITableViewCell.Type
  2. 重用池

    • 维护一个 reuseIdentifier → [UITableViewCell] 的字典。
    • 当请求 cell 时,先从池里取;若为空则根据注册的类创建新实例。
  3. 回收阶段

    • 当 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
    }
}