如何编写面向协议的TableView Sections?
嘿,针对你这个TableView多分区类型的问题,我来给你梳理几个实用的方案,帮你判断哪种更适合你的场景!
首先,你提到的associatedType方案是完全可行的,但如果你的分区类型是固定的(就是你定义的Type枚举:devices/users/status),其实还有更简洁的纯协议甚至枚举关联值的方案,下面逐个拆解:
方案一:带associatedType的协议(灵活扩展首选)
这个方案适合未来可能新增未知类型分区的场景,泛化程度最高:
// 定义带关联类型的协议 protocol SectionType { associatedtype Section associatedtype Row var sectionType: Section { get } var rows: [Row] { get } var headerTitle: String? { get } init(sectionType: Section, rows: [Row], headerTitle: String?) } // 实现通用的分区结构体 struct GenericSection<Section, Row>: SectionType { let sectionType: Section let rows: [Row] let headerTitle: String? init(sectionType: Section, rows: [Row], headerTitle: String?) { self.sectionType = sectionType self.rows = rows self.headerTitle = headerTitle } } // 使用示例 let deviceSection = GenericSection(sectionType: Type.devices, rows: [Device(name: "iPhone 14")], headerTitle: "我的设备") let userSection = GenericSection(sectionType: Type.users, rows: [User(name: "mfaani")], headerTitle: "当前用户")
优缺点:
- 优点:极度灵活,支持任意
Section和Row类型,未来新增分区类型不需要修改协议 - 缺点:需要处理泛型的类型擦除(比如写一个
AnySection包装器),才能在TableView数据源中用一个数组管理不同类型的分区,代码量会稍多
方案二:纯协议方案(固定分区场景简洁之选)
因为你的分区类型是固定的,我们可以直接把协议和Type枚举绑定,不需要泛型:
// 基础协议定义 protocol Section { var type: Type { get } var headerTitle: String? { get } func numberOfRows() -> Int func row(at index: Int) -> Any } // 每个分区类型对应一个具体实现 struct DeviceSection: Section { let type: Type = .devices let headerTitle: String? let devices: [Device] func numberOfRows() -> Int { devices.count } func row(at index: Int) -> Any { devices[index] } } struct UserSection: Section { let type: Type = .users let headerTitle: String? let users: [User] func numberOfRows() -> Int { users.count } func row(at index: Int) -> Any { users[index] } } struct StatusSection: Section { let type: Type = .status let headerTitle: String? let statuses: [Status] func numberOfRows() -> Int { statuses.count } func row(at index: Int) -> Any { statuses[index] } }
在TableView数据源中,你可以直接维护一个[Section]数组,处理起来非常直观:
var sections: [Section] = [ DeviceSection(headerTitle: "我的设备", devices: [Device(name: "iPhone 14")]), UserSection(headerTitle: "当前用户", users: [User(name: "mfaani")]), StatusSection(headerTitle: "系统状态", statuses: [Status(isOnline: true)]) ] // 数据源方法示例 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let section = sections[indexPath.section] let row = section.row(at: indexPath.row) switch section.type { case .devices: let device = row as! Device let cell = tableView.dequeueReusableCell(withIdentifier: "DeviceCell", for: indexPath) as! DeviceCell cell.configure(with: device) return cell case .users: let user = row as! User let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath) as! UserCell cell.configure(with: user) return cell case .status: let status = row as! Status let cell = tableView.dequeueReusableCell(withIdentifier: "StatusCell", for: indexPath) as! StatusCell cell.configure(with: status) return cell } }
优缺点:
- 优点:不需要泛型和类型擦除,代码逻辑清晰,每个分区的职责明确
- 缺点:新增分区类型时需要新增对应的
Section结构体,扩展性稍弱,但完全匹配你当前的固定分区场景
方案三:枚举关联值(极致简洁,固定场景最优)
如果你的分区逻辑不复杂,甚至可以直接用枚举的关联值来实现,连协议都不需要:
enum TableSection { case devices(header: String?, rows: [Device]) case users(header: String?, rows: [User]) case status(header: String?, rows: [Status]) // 封装通用属性 var headerTitle: String? { switch self { case .devices(let header, _): return header case .users(let header, _): return header case .status(let header, _): return header } } var numberOfRows: Int { switch self { case .devices(_, let rows): return rows.count case .users(_, let rows): return rows.count case .status(_, let rows): return rows.count } } }
数据源使用示例:
var sections: [TableSection] = [ .devices(header: "我的设备", rows: [Device(name: "iPhone 14")]), .users(header: "当前用户", rows: [User(name: "mfaani")]), .status(header: "系统状态", rows: [Status(isOnline: true)]) ] func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let section = sections[indexPath.section] switch section { case .devices(_, let rows): let device = rows[indexPath.row] let cell = tableView.dequeueReusableCell(withIdentifier: "DeviceCell", for: indexPath) as! DeviceCell cell.configure(with: device) return cell case .users(_, let rows): let user = rows[indexPath.row] let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath) as! UserCell cell.configure(with: user) return cell case .status(_, let rows): let status = rows[indexPath.row] let cell = tableView.dequeueReusableCell(withIdentifier: "StatusCell", for: indexPath) as! StatusCell cell.configure(with: status) return cell } }
优缺点:
- 优点:代码量最少,逻辑最直观,完全不需要额外的协议或结构体,维护成本极低
- 缺点:和纯协议方案类似,新增分区类型需要修改枚举,但对于你的固定场景来说,这完全不是问题
总结建议
- 如果未来需要支持未知的、可扩展的分区类型,选择带
associatedType的协议方案 - 如果分区类型固定,优先选择枚举关联值的方案,最简洁高效;如果需要给每个分区添加更多自定义逻辑,再考虑纯协议方案
内容的提问来源于stack exchange,提问作者mfaani
相关产品推荐
相关产品推荐

