• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

隔离 View 和 Model (Swift)

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

价值 | 思考 | 共鸣

简评:我们经常需要在编写 方便的代码 和 易于维护的代码 之间取得平衡,当然如果能兼顾两者是最好的。

在平衡便利性和可维护性时,往往会遇到 View 和 Model 需要建立联系的情况,如果 View 和 Model 建立太强的连接会导致代码难以重构和难以重用。

专用 View

先看一个例子,在构建应用时,我们需要创建用于显示特定类型数据的专用视图,假设我们需要在表格中显示用户列表。一个常见的方法是创建一个专门用于渲染用户数据的 UserTableViewCell,如下:


  1. class UserTableViewCell: UITableViewCell {

  2.    override func layoutSubviews() {

  3.        super.layoutSubviews()

  4.        let imageView = self.imageView!

  5.        imageView.layer.masksToBounds = true

  6.        imageView.layer.cornerRadius = imageView.bounds.height / 2

  7.    }

  8. }

我们还需要使用 User model 来填充 Cell 里面的内容,一般会添加一个 configure 方法,用于填充内容,代码如下:


  1. extension UserTableViewCell {

  2.    func configure(with user: User) {

  3.        textLabel?.text = "\(user.firstName) \(user.lastName)"

  4.        imageView?.image = user.profileImage

  5.    }

  6. }

上面的代码在功能上没有任何的问题,但是技术上讲,我们实际上已经将 Model 层泄露给我们的 View 层。我们的 UserTableViewCell class 不仅专门用于呈现 User 的信息(不能是其他的数据模型)而且还需要知道 User 对象的具体内容,起初,这可能不是问题,但是如果我们继续沿着这条路走,我们的 View 很容易演变成包含 app 逻辑的代码:


  1. extension UserTableViewCell {

  2.    func configure(with user: User) {

  3.        textLabel?.text = "\(user.firstName) \(user.lastName)"

  4.        imageView?.image = user.profileImage

  5.        // Since this is where we do our model->view binding,

  6.        // it may seem like the natural place for setting up

  7.        // UI events and responding to them.

  8.        if !user.isFriend {

  9.            let addFriendButton = AddFriendButton()

  10.            addFriendButton.closure = {

  11.                FriendManager.shared.addUserAsFriend(user)

  12.            }

  13.            accessoryView = addFriendButton

  14.        } else {

  15.            accessoryView = nil

  16.        }

  17.    }

  18. }

编写如上所示的代码可能看起来非常方便,但通常会让程序难以测试和维护。

通用 View

解决上述问题的一个办法是让 View 和 Model 之间进行严格的分离(代码层面和概念层面进行分离)。

我们再回去看看我们 UserTableViewCell 。他不再与 User 耦合,我们可以该名为 RoundedImageTableViewCell 并删除 configure 这个与 User 耦合的方法。


  1. class RoundedImageTableViewCell: UITableViewCell {

  2.    override func layoutSubviews() {

  3.        super.layoutSubviews()

  4.        let imageView = self.imageView!

  5.        imageView.layer.masksToBounds = true

  6.        imageView.layer.cornerRadius = imageView.bounds.height / 2

  7.    }

  8. }

进行上述更改的好处是,我们现在可以轻松的将 RoundedImageTableViewCell 和 其他 model 配合使用。

但是,在将我们的模型代码与我们的视图代码进行分离时,损失了便利性。之前我们可以使用 configure(with:) 方法来渲染 User,现在我们需要找一种新方法来实现这一点。

我们可以做的是创建一个专用对象来配置 View 的显示。我们来构建一个 UserTableViewCellConfigurator 类,代码如下(不同的架构实现有所不同):


  1. class UserTableViewCellConfigurator {

  2.    private let friendManager: FriendManager

  3.    init(friendManager: FriendManager) {

  4.        self.friendManager = friendManager

  5.    }

  6.    func configure(_ cell: UITableViewCell, forDisplaying user: User) {

  7.        cell.textLabel?.text = "\(user.firstName) \(user.lastName)"

  8.        cell.imageView?.image = user.profileImage

  9.        if !user.isFriend {

  10.            // We create a local reference to the friend manager so that

  11.            // the button doesn't have to capture the configurator.

  12.            let friendManager = self.friendManager

  13.            let addFriendButton = AddFriendButton()

  14.            addFriendButton.closure = {

  15.                friendManager.addUserAsFriend(user)

  16.            }

  17.            cell.accessoryView = addFriendButton

  18.        } else {

  19.            cell.accessoryView = nil

  20.        }

  21.    }

  22. }

这样我们能够重复使用通用 UI 代码,以及方便的在 View 中呈现 model 数据。并且我们的代码通过依赖注入可以很方便的进行测试,而不是使用单例(这部分内容可以参考这篇文章)。

无论在哪个地方想使用 User 渲染 Cell,我们都可以简单地使用我们的 configurator。


  1. class UserListViewController: UITableViewController {

  2.    override func tableView(_ tableView: UITableView,

  3.                            cellForRowAt indexPath: IndexPath) -> UITableViewCell {

  4.        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

  5.        let user = users[indexPath.row]

  6.        configurator.configure(cell, forDisplaying: user)

  7.        return cell

  8.    }

  9. }

View 工厂

configurator 非常适合可复用的 View,例如 TableViewCell ,因为他们重用的时候需要不断 re-configurator(重新配置)来显示新的 model,但对于更多的 “static” View,通常只需要配置一次就够了,因为它们渲染的模型在他的生命周期内不会改变。

这种情况下,使用工程模式可能是一个不错的选择。通过这种方式,我们可以将视图创建和配置捆绑在一起,同时仍然保持 UI 代码简单(与任何 model 分离)。

假设我们想要重建一种简单的方式呈现应用消息。我们可能会有一个视图控制器来显示一条消息。以及某种形式的通知视图。当用户收到一条新消息时弹出一个 View,为了避免重复代码我们创建一个 MessageViewFactory 让我们轻松为给定的 message 创建视图。


  1. class MessageViewFactory {

  2.    func makeView(for message: Message) -> UIView {

  3.        let view = TextView()

  4.        view.titleLabel.text = message.title

  5.        view.


    鲜花

    握手

    雷人

    路过

    鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap