展示模型
让我们看一下MVC中的一个常见挑战,以及一个已有30年历史的解决方案。 我们将其与MVVM进行对比,然后进行权衡。
我们将以“ 邮件”收件箱为例。
前言:什么是MVC?
如果您听到以下内容,请阻止我:
MVC太糟糕了! 我们有一个庞大的视图控制器,具有网络,集合视图布局和图像缓存。 我们离开了MVC,一切都已修复!
这不是针对MVC的罢工,而是我们的失败教训。 当控制器包含网络,存储或布局代码时,它不是MVC。 是“景观和泥浆球”。
该视图与演示有关,包括图形,布局和动画。 该模型涉及业务规则,包括与您的后端交谈。 控制器主要是位于视图和模型之间的胶水。 它设置场景,然后解释任一侧的事件。
在尝试新的设计模式之前,我们先弄清楚基础知识。
我们的MVC设计
- 模型:我们从
MessageStore
开始。 它具有fetchAllConversations()
方法,该方法返回一个Conversation
对象数组。 每个线程包含一个Message
对象数组。 - 视图:具有
MessageCell
表视图单元格的表视图。 - 控制器:表格视图的数据源。
在幕后,当收到新消息时, MessageStore
会收到推送通知,并检查REST端点是否有新消息。 或不。 它是模型背后抽象的实现细节。
我们要求收件箱中的每个对话都必须显示最新消息的预览。 当有新消息到达对话时,它会更新。 让我们逐步完成流程。
当MessageStore
新消息时, MessageStore
通知我们的控制器。 我们可以使用委托或NSNotificationCenter
,但这实际上取决于您。
类InboxViewController:UIViewController,UITableViewDataSource {
var对话:[对话] = []
var messageStore:MessageStore!
var tableView:UITableView!
func sessionsDidUpdate(_ notification:Notification){
对话= messageStore.fetchAllConversations()
tableView.reloadData()
}
覆盖viewDidLoad(){
super.viewDidLoad()
//设置订户等
}
}
现在让我们解决常见问题。
问题1:复杂的显示规则
让我们添加一些有关数据显示方式的规则:
- 在该消息预览中,如果最新消息是照片,则显示“ 附件:1张图像”。 否则,显示消息文本。
- 如果时间戳记是今天,则显示时间,例如“ 10:00 am”。 如果是今天之前,请显示日期,例如“ 昨天” 。
“嗯。 我们正在描述显示,为什么不将其扔到 MessageCell
呢?”
几个月后,您的公司要求一个iPad应用程序具有完全不同的单元设计。 为了共享这些显示规则,您是否将MessageCell
分为PhoneMessageCell
和PadMessageCell
?
然后,您的公司要求提供本机Mac应用程序。 (嘿,这是幻想。)MacOS不使用UITableViewCell
,因此您不能只创建MacCell
子类。
您可以在控制器中使用此逻辑,但是我们回到违反MVC的原则,此外,Mac上没有UIViewController
。
问题2:显示状态
然后是搜索框。 我们需要跟踪搜索字符串,但是将UITextField
的text
属性视为事实的来源是一个坏主意。 例如,出于性能原因,我们可能会回收视图,并且每个iOS开发人员都知道单元回收错误的痛苦。
您可以在视图控制器中跟踪此状态,但是它引入了较早的紧密耦合。
问题3:测试
假设您要测试邮件搜索,以确保它不区分大小写。 通过UIViewController
内部的业务逻辑,您需要在测试工具中包含大量的依赖项。
除了放慢测试套件的速度之外,当MessageCell
发生另一个不相关的故障时,搜索测试可能会中断。 级联的测试失败使更难追踪精确破坏的内容。
介绍演示模型
早在1988年,Smalltalk开发人员就意识到应用程序中实际上有两种类型的模型:处理业务逻辑的域模型和处理其在屏幕上表示方式的应用程序模型 。 后者有点模棱两可,所以我更喜欢Martin Fowler 2004年的品牌重塑,一种Presentation Model 。
例如, Message
是一个高级概念,属于我们的域模型。 时间戳格式规则和跟踪搜索文本属于我们的演示模型。
为了清楚起见,我将添加一个Presentation
后缀。
class ConversationPresentation {
私人var对话:对话
初始化(_对话:会话){
自我对话=对话
}
var PreviewText:String {
警卫让lastMessage = session.messages.last else {
返回“”
}
如果lastMessage.isPhoto {
返回“附件:1张图片”
}其他{
返回lastMessage.text
}
}
}
它封装了Conversation
并公开了previewText
属性以包装格式设置规则。
与其将其与Conversation
绑定,不如创建一个ConversationPresentable
协议。 在我们的测试套件中,我们将其传递给FakeConversation
以扩展所有规则。
我们还可以将搜索状态InboxPresentation
在InboxPresentation
对象中。 让我们从一些重构开始。
InboxPresentation类{
私人var store:MessageStore
var个对话:[ConversationPresentation] = []
初始化(_ store:MessageStore){
self.store =商店
self.refreshConversations()
self.subscribeToNotifications()
}
私人函式refreshConversations(){
self.conversations = store.conversations.map {转换为
返回ConversationPresentation(conv)
}
postInboxNotification()
}
func messageStoreUpdated(__ notification:Notification){
self.refreshConversations()
}
}
为简便起见,我省略了通知中心的详细信息。 简而言之,我们的视图控制器订阅了此收件箱对象。 邮件存储更新时,我们的收件箱将刷新其conversations
数组。
现在我们可以添加搜索行为:
var searchText:String? {
didSet {
refreshConversations()
}
}
私人函式refreshConversations(){
让filteredPresentations:Conversation
如果让searchText = self.searchText {
FilteredPresentations = store.conversations.filter {转换为
返回conv.bodyContains(searchText)
}
}其他{
filteredPresentations = store.conversations
}
self.conversations = filteredPresentations.map {转换为
对话表达(转换)
}
postInboxNotification()
}
我们的视图控制器只是读取:
func searchFieldDidUpdate(_ sender:Any){
self.inbox.searchText = self.searchField.text
}
除了清理视图控制器之外,我们还隐藏了有关过滤消息时所涉及实体的详细信息。 稍后,我们可以重构事物以直接对数据库进行单个SQL调用。
请注意,演示模型与您可能听到的演示者不同。 从技术上讲,Apple的MVC是Model-View-Presenter模式。 不管出于什么原因,苹果公司都将其更名。 如果看到Presenter,则认为是Controller 。 如果您看到Presentation Model,那就是我们刚刚讨论的事情。
严格来说,福勒的演示模型会接收所有事件。 如果您的视图控制器是视图的委托,则您不会使用他的Presentation Model。 我认为这是一个很小的区别。 即使在MVC中,您也可以在“被动视图”和“监督控制器”之间找到一个范围。我认为还有很多术语可以避免……
MVVM呢?
如果您阅读过多的iOS博客,那么现在您的意思是:“这听起来很像MVVM。”人们一直在说MVVM,但我认为这并不意味着他们认为的含义。 如果您对软件人类学感兴趣,请继续阅读。 否则,最后跳到“权衡”。
福勒悬而未决:
您必须在Presentation Model中进行同步的一个特殊决定是哪个类应包含同步代码。
在2005年,Microsoft接受了Fowler的想法并付诸实践。 他们将MVVM编码为一个宏伟的目标:
[MVVM]专为现代UI开发平台量身定制,在该平台中,视图是设计者而非经典开发者的责任。 设计师通常是更具图形,艺术专心的人,与传统开发人员相比,经典代码的编写较少。 设计几乎总是以声明性形式(例如HTML或XAML)完成,并且经常使用诸如Dreamweaver,Flash或Sparkle之类的WYSIWYG工具进行设计。
Microsoft承认它受到了表示模型的启发:
它看起来惊人地类似于Presentation Model模式…实际上,唯一的区别是WPF和Silverlight的数据绑定功能的显式使用。
他们以为工程师只会将ViewModel扔给他们的设计师,而设计师可以在不编写任何代码的情况下围绕它构建GUI。 他们通过MVVM的最大定义特征来完成此任务: 数据绑定 。
但是,微软对MVVM的营销远不只是“一种利用绑定的有用设计模式。”微软出售MVVM作为其替代MVC的平台:
MVVM模式在某些方面类似于Model-View-Presenter(MVP)模式…两种模式都是Model-View-Controller(MVC)模式的变体,都是分离的展示模式,并且都设计为隔离细节从底层业务逻辑的用户界面,以增强可管理性和可测试性。
这将我们带到了MVVM的第二大特点:ViewModel 代替了Controller。 它是MVVM,而不是MVMVC。
视图模型还提供了应用程序的用户在视图中启动的命令的实现。
然后是太多的iOS开发人员误解了MVVM。 大多数iOS示例不使用绑定-考虑到iOS不提供绑定,这是有道理的。
在许多示例中,视图控制器仍然充当事件的中心枢纽。 这是有道理的,因为只有控制器才能获取基本事件,例如viewDidAppear
和viewDidAppear
事件。 没有逃脱的MVC。
大多数人说MVVM的时候,他们实际上是在谈论更接近于Presentation Model的事物。 “ iOS MVVM”是一种反映。 是WaLuigi。
这些语义重要吗?
“人们真正表示表示模型时说MVVM是否重要? 甚至苹果公司也称MVC为真正的MVP。”
如果您的团队已将您的演示模型称为“视图模型”,则不值得重命名所有内容。 就我而言,将其称为杯形蛋糕模型。
但是,正如您在我重复的“苹果的MVC”中所注意到的那样,当有人选择现有术语时,这会造成混淆。 不幸的是,坚持要求苹果在其代码UIViewPresenter
UIViewController
重命名为UIViewPresenter
是很疯狂的。 但是这艘船还没有驶向您的团队。
我知道这是自行车脱落,但是如果我需要为这些类型的型号添加后缀,则倾向于“展示”。 为什么?
- ViewModel的重载含义给Google结果带来了影响
- 演示文稿描述了对象的职责。 见经理
- 这是一件很小的事情,但是“快速打开”所产生的“ pres”结果要少于“ view”结果。
权衡取舍
需要强调的是,表示模型不是新的体系结构。 不要将其粘贴在每个项目中,并说您正在练习MPVC。
考虑一个待办事项列表应用程序。 您有一个Task
对象,并决定添加TaskPresentation.
class TaskPresentation {
var task:Task
init(_ task:Task){
self.task =任务
}
var text:String {
返回self.task.text
}
var Important:布尔{
返回self.task.important
}
}
您最终会获得很多没有附加值的样板。 Swift可以提供工具来简化此过程吗? 我们不要去那里。
接下来,我们遇到诸如“ unread
属性将属于表示层或域层吗?”这样的问题,请当心,否则最终结果将是随机分布在两个位置之间的行为。 这可能比原始问题更严重。
最后,有句老话:“计算机科学中的所有问题都可以通过另一层间接解决,除非存在太多的间接层。”
在前面的示例中,我们有ConversationPresentation
,但是如果为对话屏幕添加另一个演示模型,该怎么办? 我们最终将获得ConversationInboxPresentation
和ConversationDetailPresentation
。 这听起来像企业软件。
在真实的项目中,从一组简单的实体开始。 不要只介绍演示模型来包装几个 陈述。 任何说“一切都必须经过考验”的人都生活在幻想中。 但是,这是主观的。
- 使用Slather将代码覆盖范围添加到Zendesk的iOS SDK版本中
- BUG – IOS中的select标记没有完成button
- 错误:使用LinkedIn的REST Api for iOS / Like / Different和Comment
- 如何禁用自定义静态UITableViewCell的可访问性
- parsing – 使用关系与指针?
- 为什么“模拟后台获取”导致崩溃(libsystem_kernel.dylib`mach_msg_trap),因为Xcode 8?
- DynamoDB自动增加ID和服务器时间(iOS SDK)
- 我们如何从贝宝获得交易费用金额?
- 如何编写UI测试来测试在UITableView中单击第一个单元格时是否存在图像?