列表和项集合可以说是应用程序中最常见的两种UI。无论我们是构建用于浏览内容,与他人通信还是进行购买的应用程序,许多应用程序都通过a UITableView或a 来呈现其大部分数据UICollectionView。
表视图和集合视图的一个共同点是它们使用数据源对象为它们提供要显示的单元格和数据。虽然这种模式最初看起来非常繁重,并且是样板的主要来源 - 但这是使这种类型的视图能够轻松地重用单元并代表我们进行大量性能优化的核心部分。
本周,我们来看看如何以更可重用的方式实现表视图和集合视图的数据源,以及如何让我们使基于列表的UI代码更易于组合和更易于使用。虽然本文中的所有代码示例都将基于表视图,但完全相同的技术也可用于集合视图。我们潜入吧!
同时小编这里有些书籍和面试资料哦(点击下载)

离开视图控制器
就像其他与显示和处理来自UI的交互密切相关的代码一样,数据源实现很容易在我们的视图控制器中结束。例如,假设我们正在构建一个电子邮件客户端应用程序,并且我们正在使用a UITableView
来呈现用户的收件箱InboxViewController
。一个非常常见的解决方案是让视图控制器充当其表视图的数据源,如下所示:
extension InboxViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return messages.count
}
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let message = messages[indexPath.row]
let cell = tableView.dequeueReusableCell(
withIdentifier: "message",
for: indexPath
)
cell.textLabel?.text = message.title
cell.detailTextLabel?.text = message.preview
return cell
}
}
虽然上面的方法适用于不包含大量逻辑的更简单的一次性表视图控制器,但如果我们需要让视图控制器执行其他几项任务(通常是这样的话),事情就会变得非常混乱案例) - 或者我们想在我们的应用程序的其他地方重用相同的数据源逻辑。
例如,我们的电子邮件应用程序需要在许多不同的地方呈现消息的可能性非常高 - 例如,在包含已发送消息,已存档消息或可能在“草稿”或“ 文件夹”视图中的列表中。在这种情况下,能够轻松地重用我们的数据源代码可能非常好,因为它可以让我们非常快速地构建基于相同底层数据模型的新UI。
这样做的一种方法是简单地将所有与数据源相关的逻辑从我们的视图控制器移到单独的类中 - 并使这些类符合UITableViewDataSource
,如下所示:
class MessageListDataSource: NSObject, UITableViewDataSource {
// We keep this public and mutable, to enable our data
// source to be updated as new data comes in.
var messages: [Message]
init(messages: [Message]) {
self.messages = messages
}
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return messages.count
}
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let message = messages[indexPath.row]
let cell = tableView.dequeueReusableCell(
withIdentifier: "message",
for: indexPath
)
cell.textLabel?.text = message.title
cell.detailTextLabel?.text = message.preview
return cell
}
}
上述方法的好处是我们开始转向更多的插件式 UI架构,我们可以通过插入现有的构建块快速轻松地组装新的UI和功能。就像子视图控制器一样,一旦将它们分解成可以单独处理的单独部分,事情往往变得更容易推理。
泛化
将代码分解为单独的类型可以是避免大规模类的一个很好的解决方案,但有时可能导致代码重复。虽然重复并不一定是坏事 - 有时一些轻复制比创建太宽且难以定制的抽象更好 - 但通常不需要重复编写高度相似的代码。
对于表和集合视图数据源代码,我们经常希望为许多(有时甚至是所有)模型执行完全相同的任务。我们通常希望为每个模型实例显示一个单元格,并且我们总是需要为每种类型的数据执行相同的出列舞蹈。
让我们看看我们是否可以引入一个非常轻量级的抽象来使这个更好,通过推广我们之前的专用数据源类。给定一个单元重用标识符和一个为给定模型配置表视图单元的闭包Message
,让我们创建一个可以渲染任何模型的泛型类,而不是让渲染模型特别依赖于渲染模型。
class TableViewDataSource<Model>: NSObject, UITableViewDataSource {
typealias CellConfigurator = (Model, UITableViewCell) -> Void
var models: [Model]
private let reuseIdentifier: String
private let cellConfigurator: CellConfigurator
init(models: [Model],
reuseIdentifier: String,
cellConfigurator: @escaping CellConfigurator) {
self.models = models
self.reuseIdentifier = reuseIdentifier
self.cellConfigurator = cellConfigurator
}
}
上面我们使用基于闭包的方法,但另一种方法也可以是使用配置器模式,例如“防止视图在Swift中被模型识别”。
有了上述内容,我们现在可以在UITableViewDataSource
不知道任何模型实现细节的情况下实现,只需使用Model
我们的数据源初始化程序中注入的数组和配置闭包 - 如下所示:
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return models.count
}
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let model = models[indexPath.row]
let cell = tableView.dequeueReusableCell(
withIdentifier: reuseIdentifier,
for: indexPath
)
cellConfigurator(model, cell)
return cell
}
现在,每当我们需要一个数据源来显示模型数组时,我们就可以简单地创建一个实例TableViewDataSource
,例如替换我们之前的实例MessageListDataSource
:
func messagesDidLoad(_ messages: [Message]) {
let dataSource = TableViewDataSource(
models: messages,
reuseIdentifier: "message"
) { message, cell in
cell.textLabel?.text = message.title
cell.detailTextLabel?.text = message.preview
}
// We also need to keep a strong reference to the data source,
// since UITableView only uses a weak reference for it.
self.dataSource = dataSource
tableView.dataSource = dataSource
}
很简约!👍而且很酷的是我们现在可以使用相同的类来渲染我们最有可能拥有的任何其他模型 - 例如联系人,草稿,模板等等 - 我们甚至可以添加静态便捷方法,使其更加容易为我们最常见的模型创建数据源:
extension TableViewDataSource where Model == Message {
static func make(for messages: [Message],
reuseIdentifier: String = "message") -> TableViewDataSource {
return TableViewDataSource(
models: messages,
reuseIdentifier: reuseIdentifier
) { (message, cell) in
cell.textLabel?.text = message.title
cell.detailTextLabel?.text = message.preview
}
}
}
添加这些便捷方法不仅可以进一步减少重复代码的需要,还可以让我们以非常好的方式使用点语法创建数据源:
func messagesDidLoad(_ messages: [Message]) {
dataSource = .make(for: messages)
tableView.dataSource = dataSource
}
像上面这样的变化一开始可能看起来很简单,但实际上可以对开发人员的工作效率产生很大的积极影响,特别是当我们正在开发需要快速迭代和实验的应用程序时 - 因为创建大多数数据源不再是一件大事。
撰写部分
让我们继续探索可重用数据源的概念。到目前为止,我们只实现了渲染单个部分的数据源,但有时我们想要渲染多个数据源,每个数据源都有自己的数据类型。例如,在我们的电子邮件应用中,我们可能希望实现一个HomeViewController
向用户显示最近联系人列表以及来自用户收件箱的热门消息。
虽然这可以使用一个新的专用对象来完成,该对象获取主屏幕的所有数据并通过自定义UITableViewDataSource
实现提供它,但我们尝试使用组合将多个小数据源组合在一起以形成我们需要的数据源。
为此,让我们实现一个SectionedTableViewDataSource
简单地获取其他数据源的数组,并使用它们中的每一个在其提供数据的表视图中形成一个部分。我们将从这样开始:
class SectionedTableViewDataSource: NSObject {
private let dataSources: [UITableViewDataSource]
init(dataSources: [UITableViewDataSource]) {
self.dataSources = dataSources
}
}
然后,我们将UITableViewDataSource
通过将大多数调用转发到与当前索引路径的节索引匹配的基础数据源来符合:
extension SectionedTableViewDataSource: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return dataSources.count
}
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
let dataSource = dataSources[section]
return dataSource.tableView(tableView, numberOfRowsInSection: 0)
}
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let dataSource = dataSources[indexPath.section]
let indexPath = IndexPath(row: indexPath.row, section: 0)
return dataSource.tableView(tableView, cellForRowAt: indexPath)
}
}
有了上述功能,我们现在可以轻松创建几乎没有代码重复的分段数据源 - 并结合我们的便利API,用于创建以前的模型特定数据源,我们可以开始编写由多组模型组成的数据源缓解:
let dataSource = SectionedTableViewDataSource(dataSources: [
TableViewDataSource.make(for: recentContacts),
TableViewDataSource.make(for: topMessages)
])
这实际上是组合的强大功能,事实上我们并不总是必须从一种新类型开始,而是通常可以从现有类型中构建功能。
结论
通过编写更通用的数据源,我们通常最终可以在应用程序的多个部分重用相同的代码,在这些部分中呈现相同类型的模型。由于数据源主要基于索引路径,因此它们也非常适合于组合 - 因为数据源的核心功能通常可以以完全模型不可知的方式执行。
虽然有时候定制,高度专业化,实现UITableViewDataSource
或UICollectionViewDataSource
有序 - 可重用数据源(例如本文中的示例)的目标是消除编写数据源时经常涉及的大量样板和复制。执行简单的数据绑定。
你怎么看?您是否经常将数据源构建为单独的可重用对象 - 或者您将尝试使用它?请请通过加我们的交流群 点击此处进交流群 ,来一起交流或者发布您的问题,意见或反馈。
谢谢阅读~点个赞再走呗!🚀
原文地址 https://www.swiftbysundell.com/posts/reusable-data-sources-in-swift
网友评论