使用ViewModel配置UITableViewCell的规范及日期格式化咨询
Hey there! Let's dive into your two questions about setting up ViewModels for UITableViewCells—your current implementation is already heading in the right direction, so let's unpack this clearly.
1. Cell的ViewModel协议:应该包含格式化后的展示属性还是持有Item对象?
你当前的做法是完全正确的:协议应该暴露直接用于UI展示的格式化属性,而不是让ViewModel持有原始的Item对象。
原因很简单:ViewModel的核心职责就是作为数据转换层,把原始模型(Item)转换成UI组件(Cell)能直接使用的数据。这样做有几个关键好处:
- 解耦Cell与原始模型:Cell不需要知道Item的内部结构(比如
date是Date类型),它只需要拿到要展示的字符串即可。如果以后Item的属性发生变化(比如date字段改名),只需要修改ViewModel的转换逻辑,Cell可以完全不动。 - 单一职责原则:Cell专注于UI渲染,ViewModel专注于数据格式化,原始模型专注于数据存储,各司其职,代码更易维护。
- 避免UI层依赖业务模型:如果Cell直接依赖Item,很容易在Cell里写业务逻辑,导致Cell变得臃肿难以复用。
你的ItemTableViewCellRepresentable协议设计得很合理:只暴露date: String和info: String?这两个Cell需要的展示属性,完全符合这个原则。
2. 日期格式化的职责与DateFormatter的复用问题
谁负责格式化?
你的判断没错:Cell的ViewModel应该承担日期格式化的职责。这正是ViewModel存在的意义——把原始的Date类型转换成用户可见的字符串格式,Cell只负责把这个字符串显示出来,不需要关心格式化逻辑。
DateFormatter的效率问题
你担心的点很到位:DateFormatter是一个相对重量级的对象,创建和配置它的成本较高,如果每个Cell ViewModel都创建一个新实例,会造成不必要的性能损耗。
你的解决方案(从外部传入已初始化的DateFormatter)是最优解,这样可以保证整个页面只维护一个DateFormatter实例,避免重复创建。
可以再优化一下这个思路:
- 在主ViewModel中初始化一次
DateFormatter(配置好你需要的格式,比如"yyyy-MM-dd HH:mm"),然后在创建Cell ViewModel时传递给它。 - 如果你在多个页面都需要相同的日期格式,可以把
DateFormatter封装成一个单例类,或者通过依赖注入的方式传递,进一步复用。
比如你可以创建一个全局的Formatter工具类:
class FormatterManager { static let shared = FormatterManager() let cellDateFormatter: DateFormatter private init() { cellDateFormatter = DateFormatter() cellDateFormatter.dateFormat = "yyyy-MM-dd HH:mm" cellDateFormatter.locale = Locale.current } }
然后在主ViewModel里直接使用FormatterManager.shared.cellDateFormatter,再传递给Cell ViewModel,这样整个App里都复用同一个实例,性能更优。
对你当前代码的小优化建议
你的代码已经很不错了,这里提两个小细节:
- 可以去掉
ItemTableViewModel的空初始化方法,改用可选初始化或者处理item为nil的情况更明确:
class ItemTableViewModel: ItemTableViewCellRepresentable { var date: String var info: String? required init(item: Item, formatter: DateFormatter) { self.date = formatter.string(from: item.date) self.info = item.info } // 处理item为nil的情况,返回一个默认状态的ViewModel static func empty() -> ItemTableViewModel { return ItemTableViewModel(item: Item(), formatter: FormatterManager.shared.cellDateFormatter) } }
然后在viewModelForCell里调用:
private func viewModelForCell(at index: Int) -> ItemTableViewCellRepresentable { guard let item = viewModel?.items[index] else { return ItemTableViewModel.empty() } return ItemTableViewModel(item: item, formatter: viewModel.cellDateFormatter) }
- 可以把
setupCell()方法改成监听viewModel的属性变化(比如用didSet),这样当ViewModel赋值时自动更新UI,不需要手动调用:
var viewModel: ItemTableViewCellRepresentable? { didSet { setupCell() } } private func setupCell() { guard let viewModel = viewModel else { dateLabel.text = "" descriptionLabel.text = "no info" return } dateLabel.text = viewModel.date descriptionLabel.text = viewModel.info ?? "no info" }
这样在cellForRowAt里就可以去掉cell?.setupCell()调用,代码更简洁。
内容的提问来源于stack exchange,提问作者mikro098

