Tag: 软件体系结构

架构A / B实验(iOS)

对产品进行实验是公司获得其客户最佳响应的最强大的技术之一。 但是有时候,尤其是当它们同时超过2个或3个时,对于在整个项目中找到实验块的开发人员来说,这可能会有些沮丧。 在本文中,我将解释一种简单的体系结构方法来保持我们的代码干净和动态。 什么是A / B测试? 假设我们要改善应用程序的功能,以吸引更多用户使用它。 我们几乎没有改善它的想法(涉及设计,可见性等),但是我们不确切知道哪一个最能使用户获得最大响应。 解决方案是设置A / B测试 。 A / B测试是根据特定版本随机分配用户的实验。 让我们看看它是如何工作的: 假设我们想知道哪个是最好的价格,我们应该出售我们的高级会员才能获得最高的收入。 我们决定设置3种变化: 原始价格变化 :15,00 $; 变化A :25,00 $; 方案B :40,00 $。 通过特定API(即Apptimize API)使用我们应用程序的每个用户都将被分配为这些变体之一,并且该服务将向我们的客户返回已分配用户的变体。 此时,根据选择的变体,我们将执行一段代码而不是另一段代码。 在上面的示例中,用户将看到为其分配的变体选择的价格。请注意, 实验必须始终包含原始变体 ,换句话说,就是开始实验之前存在的变体,否则我们永远不会知道这是否是最好的解决方案,或者实际上是否还不够好。 做得好,实验正在运行! 现在,我们只需要等待任意时间即可获得结果。 好的,已经过去了一周,我们发现了以下结果: 原始变体: 1000个用户支付了15,00 $-> 15.000,00 $的收入 ; 变体A: 800个用户支付了25,00 $-> 20.000,00 $的收入 ; 变体B: 300个用户支付了40,00美元-> 12.000,00美元的收入 ; 请注意,最低价或最高价均未赢得! 实际上, 变体A […]

实用的iOS应用架构

最近,我一直在阅读许多有关应用程序体系结构的文章。 有很多这样的文章,有很多不同的见解和解决方案。 开发人员分享他们的经验,优缺点可能会帮助我们决定在未来的项目中走哪条路,这是很好的。 我同意,有许多很好的架构,经过精心设计,并具有很好的关注点分离,可以解决其他方法的弊端。 但是,我也认为没有适合所有项目的应用程序架构。 我们如何衡量架构对项目是否有利? 好吧,我认为此评估中有几个相关参数。 评估架构 首先,应将应用程序的组件合理组织和分离。 他们不应该对其他组件的内部细节了解太多。 其次,正如鲍伯叔叔所说的那样,体系结构应该对项目的业务领域“尖叫”。 它是运输应用程序吗? 也许一个金融机构? 这是您从项目中获得的信息,只需以新手的身份快速浏览一下代码即可。 拥有不言而喻的架构对于维护和增长产品至关重要,尤其是在增加人员时。 然后,我们当然具有可伸缩性。 添加更多功能有多容易/难? 拥有一个优雅的解决方案可能会在将来为我们节省大量时间和金钱。 另一个参数是架构如何适合业务领域的要求。 它是繁重的数据驱动的应用程序吗? 它有很多需要用户输入的表格吗? 我们将要构建的应用程序的复杂性是什么? 是“ 5个屏幕”应用程序还是“ 50个屏幕”应用程序? 需要考虑的另一件事是开发团队的效率。 团队能否迅速了解新架构和可能不同的概念? 他们将能够独立处理功能而不会阻塞自己吗? 想象一下,只有一个故事板的架构,团队中的每个人都必须在用户界面上工作。 合并地狱等待发生。 测试是体系结构决策中的另一个重要元素。 我们要测试哪些关键组件? 在测试方面,我属于认为必须只测试值得测试的零件的小组。 我不喜欢做很多无用的测试,只是增加了代码覆盖率。 代码覆盖率只是一个欺骗性的统计信息。 您可能有90%的测试覆盖率,但是错过了关键部分。 在另一个极端,我看到了设计完美的项目,这些组件具有很好的隔离性,覆盖率为零。 在决定应用程序体系结构时,这使我想到了另一个关键部分-上下文。 项目的截止日期和预算是多少? 我们必须在质量(交货时间)上进行哪些权衡? 工程师需要更多时间以最优雅的方式设计和实施项目,而销售则需要更快的时间。 每个人都有自己的利益,我们必须意识到双方的平衡。 低质量的产品不会持续很长时间,但是市场上可能不再需要后期产品。 这就是为什么我认为在进行体系结构决策时,我们需要务实,中立和对全局的理解,而对我而言,这是做出此类决策的动力。 我们不需要偏向任何一种方法,也不应抛弃其他方法。 这只会限制我们的工具集,并在做出决策时缩小我们的可能性。 好老的MVC 我很困惑为什么这么多人只是放弃了Model-View-Controller模式。 他们代表Massive View Controllers。 它不适用于大型项目。 好吧,我已经看到(并从事过)非常大的项目,这些项目很好,干净且可维护,并且遵循Model-View-Controller。 我们有很多概念和模式可以帮助我们减小视图控制器的大小,例如委派,组合,依赖注入,协议,纯函数,服务/实用程序类,导航中心。 有了这些技术,测试也不是那么困难。 […]

使用VIPER模式进行iOS开发的干净架构

当开始一个iOS项目时,开发人员除了要达到应用程序的目的之外,还将首先关注的障碍之一是,他们需要的Cocoapods将是如何组织代码,以及可能遵循的设计模式。 尽管大多数开发人员会坚持使用久经考验的真正的MVC(模型-视图-控制器)或MVVM(模型-视图-视图模型),但是有一种聪明的模式称为VIPER,许多人都不知道。 VIPER可能会改变您习惯用于iOS平台的开发方式,并且像大多数事物一样,它具有积极和消极的作用。 通常的嫌疑 首次开始iOS开发时,开发人员将听到很多有关MVC或Model-View-Controller的信息。 这种Apple认可的架构模式随处可见,包括Apple的UIKit,大多数教程示例应用程序以及当今App Store上的大多数应用程序。 顾名思义,MVC分为三个职责: 模型:应用程序的数据和操纵该数据的逻辑。 视图:用户操作的用户界面。 控制器:控制模型和视图之间的逻辑。 如果您正在开发第一个应用程序或在一个小型团队中工作,那么MVC可以很好地工作,但是随着应用程序的发展,开发人员开始开玩笑地将MVC称为“大型视图控制器”。随着时间的流逝,越来越多逻辑被推到控制器中,控制器变得becomes肿且不可测试。 这就是VIPER模式试图解决的问题。 什么是VIPER? VIPER模式是一种遵循单一职责原则的干净架构。 VIPER努力将应用程序的逻辑划分为不同的责任层。 与MVC相比,VIPER更进一步,它分为五个职责: 视图:显示来自演示者的信息,并将用户交互发送回演示者。 交互者:检索实体,并包含特定用例的业务逻辑。 它们与视图无关,可以由一个或多个演示者使用。 演示者:处理为显示准备的内容并拦截用户交互。 实体:简单的数据模型对象。 路由器:处理应显示哪些屏幕以及何时显示的导航逻辑。 实施VIPER时,每个功能或模块都将遵循上述结构。 由于该应用程序的逻辑将分为多个较小的组件,因此视图现在变得更明亮,逻辑也变得更具可测试性。 VIPER的流程 VIPER的基本流程非常简单。 路由器将用户带到新的视图,该视图通知演示者它需要数据,演示者向交互器询问数据,交互器检索实体(从网络请求或本地数据库),交互器将实体发送到演示者,演示者从实体创建视图模型,演示者将视图模型发送到视图,然后视图向用户显示必要的数据。 实施VIPER 为了演示以VIPER模式创建模块,让我们假设我们正在创建一个显示汽车表的应用程序。 每个单元格都会显示汽车的品牌和型号。 用户将能够点击一个单元格并查看汽车的详细信息,或者他们可以单击“创建新汽车”按钮将新汽车添加到列表中。 实施新模块时,我发现自下而上的工作会更容易,因此我们将从定义实体开始。 实体 由于该应用程序正在处理汽车,因此让我们创建一个简单的struct对象,该对象将包含一些基本信息: car对象。 car对象是我们的API服务将返回给我们的东西。 它包含基本信息,例如汽车的ID,品牌,型号和内饰。 但是,当我们要显示有关汽车的信息时,由于表格单元格仅需要显示汽车的制造商和型号,因此不需要包括所有信息。 因此,我们可以创建一个快速视图模型来仅代表汽车的制造商和模型。 快速视图模型将在Presenter中创建,并传递回View。 互动者 现在我们的实体已经建立,让我们为其创建业务逻辑或“用例”。 我们的表格视图将需要使用API​​服务中的汽车填充。 因此,我们将创建一个Interactor,用于处理从API检索汽车并将其发送到Presenter。 为此,我们声明了一个名为getCars的协议方法,该方法将使用我们的API服务获取汽车并将其返回。 由于我们的应用程序非常简单,因此我们不需要其他用例(尽管大多数实际应用程序都有多个用例)。 主持人 有了Interactor,我们现在有了一种方法来检索最终将要显示的汽车。 如前所述,演示者负责对用户的输入做出反应并为显示准备内容。 我们的应用程序描述中提到需要显示汽车(制造商和型号),显示汽车详细信息的能力以及创建新汽车并将其添加到表格中的能力。 接下来,我们将创建一个Presenter,使我们能够做到这一点。 为了展示汽车,我们添加了showCars方法,该方法使用我们先前创建的Interactor检索汽车,然后从这些对象创建简单的视图模型,这些对象将用于视图中特定类型的单元格。 接下来的两个方法showCarDetail和showCreateCarScreen将使用Router(将在下面创建)来将用户导航到正确的屏幕。 我们的视图将使用以下三种方法来启用功能。 […]

iOS的uma arquiteturarobusta em seu projeto iOS

Estéartigo pretende trazer ao seu leitor,软件开发者,软件开发者,可移动iOS应用程序的速记4 SOLID和KISS解决方案的重要性软件quando desejamos construir umasoluçãorobustaesustentável。 Arquitetura软件 从本质上说,使用高质量的语音软件可以通信和使用Carinho。 可以使用部分软件,也可以使用ganhar novas funcionalidades软件。 一种由软件组成的软件,它可以解决软件开发中的任何问题。 Neste linkháum materialtambémescrito por mim quepoderáte explicar mais sobre o problema。 通用软件安装软件鲁棒软件,依赖软件。 界面和接口的实现以及实现的接口。 EstePadrãonos ajudatambém是princípiodainversãodadependência (SOLI D )的代表。 蟒蛇 Como boaspráticas, POO的公用事业概念,通用的公用事业RXSwift公用程序。 Estaspráticas,pro deto的长期运作,nos ajudaram和SOLI D restantes的回应。 没有iOS的重要事项 Este artigoabordaráo或us e dedeçênciautilizando使用了一个故事板 ,并推荐了一个UI。 使用casovocênão,使用nãopoderáusar ométodo 准备(用于segue :) pois作为seguessão类,特别用于故事板 。 […]

Swift中的关联类型

动机 可重用性是软件开发中的重要组成部分。 我认为,软件体系结构中最重要的考虑因素之一就是即将推出的功能的实现时间。 我经常有片刻,然后躺在床上,直到实施了该死的功能后才入睡。 因此,快速的实施时间可以为我节省大量的睡眠时间(这是我绝对需要的)。 继承是重用超类功能的一种方法。 但是事情会迅速发展,随之而来的是复杂性的增加。 幸运的是,我们在Swift中有了面向协议的方法。 但是协议必须是通用的,以便将其定义重新用于不同的类型。 类允许我们指定通用参数。 协议为我们提供了“关联类型” 。 实际示例:用于CoreData处理的CRUD协议 我想为我的CoreData实现创建一个协议。 这将是常见的CRUD (创建,读取,更新,删除)功能。 没有通用协议,我必须在所有地方都使用NSManagedObject,这真的很不舒服,因为我有Xcode生成的子类。 我的实体是: TaskMO和ItemMO 。 我都需要他们的经理班。 对于TaskMO,协议定义必须匹配一次,对于ItemMO,协议定义必须匹配一次。 可以使用“ associatedtype ”关键字来实现。 协议持久{ 关联类型实体 } 如您所见,实体只是一个占位符,而不是具体类型。 具体类型将仅在实现中指定。 使用约束来缩小类型 Persistable协议使我们能够处理CRUD功能。 但是我要确保将其用于CoreData。 我们可以通过向我们的关联类型添加约束来确保这一点。 就我而言,我只想允许在NSManagedObject子类上使用。 此步骤与通用参数非常相似。 协议持久{ relatedtype实体:NSManagedObject } 完整协议如下所示: 编写实现 现在按照您习惯的方式实现类型。 名为“类型推断”的编译器功能会自动检测具体类型的定义。 因此,我们不必明确指定它。 煮咖啡机 凉。 我们创建了一个通用的可重用协议。 但是,让我们深入一点。 我想制造一台咖啡机。 不只是基本的黑咖啡。 我想要一杯很棒的拿铁玛奇朵。 首先,我指定成分: 我的咖啡由主要的咖啡成分和某种牛奶组成。 我将协议命名为“ Drinkable […]

VIPER — Arquitetura limpa em nossos应用程序

Gosto da类比entent软件e carro。 没有可用于公用事业的软件,也不能使用temos camadas de componentes quesãose parapara por forutilização软件。 我在Locost七号航站楼。 莲花莲花的七种风情七种安全和良好的保护作用(nãoque isso sejafácil)。 Logo de cara,一种外在或外在的念珠菌。 没有人可以定义estEstéePropropósito,Basta olhar或Locost e Perceberééééum carro de Corrida com apelo年份。 没有软件可以真正实现对系统的信息传输。 百事大合唱团存在于乌干达埃斯特鲁图拉基地,区别于基本的加德罗。 Quando carro de Corrida,necessita de uma estrutura forte para aguentar disputas,mas ao mesmo tempo leve para ter um bom desempenho。 没有任何人可以在Locost那里,在Alémde Ter uma estrutura pensada para […]

设计iOS社交网络应用架构

最近,我发布了名为Socium的iOS社交网络应用模板。 在这里,我想与您分享我从开发中学到的知识。 功能清单 首先,我们需要写下要在应用程序中看到的功能列表。 例如,Socium与典型的社交网络应用程序一样,具有以下功能: 用户资料 用户帖子和评论 发表喜欢 浏览和搜索其他用户 私人实时聊天 追随者/追随者 推送通知 考虑到此列表,我们可以为项目选择一个后端。 后端 社交网络应用程序是一个复杂的系统,由客户端和服务器端组成。 Socium的服务器端建立在Parse Server之上。 Parse Server有许多优点:它是开源的,具有庞大的社区,并且具有出色的iOS SDK(顺便说一下,还有Android SDK)。 Parse Server需要VPS或Heroku之类的云应用程序平台,对于不熟悉后端内容的用户而言,可能难以设置和维护。 但是,我相信控制后端是一件好事。 即使需要一些其他技能。 毕竟,我从课程中学习。 数据库架构 现在,当我们知道要求时,就该写下所有必要的表及其之间的关系了。 这是我为Socium设计的架构: 让我们来看几个最有趣的实体(或Parse Server术语中的“类”)。 用户 / UserProfile 。 为了保护用户的电子邮件,我为公共用户配置文件创建了另一个类UserProfile,并与User类添加了一对一的关系。 任何人都可以看到UserProfile 。 用户仅对所有者和管理员可见。 UserProfile具有followings , followers和postLikes字段,它们分别是与UserProfile和Post实体的一对多关系。 他们全部 图片 。 我没有将图像直接存储在PFFile字段中,而是为此创建了一个单独的类。 因此,图像可以阵列和多对多关系存储。 如果我们想在照片中添加喜欢/不喜欢的功能,则可以提供更好的灵活性。 Post , PostComment , Conversation和Message实体非常不言自明。 客户端应用架构 定义数据库架构后,该考虑一下我们的iOS应用了。 […]

在Swift中使用通道进行数据流📻

最初发表在 Swift Post上 。 Apple框架大量使用委托和观察者模式(NotificationCenter)来传递信息。 尽管这些模式没有错,但实际的实现始终对我来说有点矛盾。 首先让我们看一下这些模式的基本特征: 委派:支持一对一的双向通讯。 观察者 :支持一对多,单向通信。 让我们看一些UITableViewDelegate方法。 可选的func tableView(_ tableView:UITableView,heightForRowAt indexPath:IndexPath)-> CGFloat UITableView :发件人 UITableViewDelegate (主要是 UIViewController ) :接收器 此方法是一对一双向通信的一个很好的例子。 由于表格视图要求控制器返回高度,因此通讯是双向的,不能一对多。 (否则,我们将无法决定应使用哪个返回的高度值。) 但是,以下方法不返回任何内容。 可选的func tableView(_ tableView:UITableView,didSelectRowAt indexPath:IndexPath) 在此,发送方仅通知接收方。 因此,通信是单向的,可以是一对一或一对多。 在这种情况下,授权是一个不错的选择吗? –也许… 如果我们需要通知多个组件会怎样? –我们不能。 如果我们不在乎行选择,而是想自己提供行高怎么办? 我们还需要提供空的实现吗? –如果这是一个纯Swift协议, 是的 ,我们将需要为每个我们不关心的委托方法准备空的实现,这很烦人。 由于UITableViewDelegate继承自Objective-C,因此为了向后兼容,我们可以将方法标记为可选。 但是Swift出于某种原因禁用了此“功能”。 具有庞大的协议会违反接口隔离原则,并使它们无法读取。 表视图是我作为开发人员生活的重要部分,但我仍然不记得UITableViewDelegate和UITableViewDataSource协议中定义的内容,因为列表太长了。 那么……为什么我们不对每个单向动作都使用观察者模式呢? 我们可以提供具有更大灵活性的相同功能,并且还可以清除大型委托协议中的大多数方法。 我们有一个设置页面,其中包含主题选择。 每当主题更改时,我们都希望更新主屏幕。 解决方案1:使用委派 此解决方案有效,但是否足够灵活? 如果我们有5个选项卡要与此主题更改进行更新,会发生什么情况? 解决方案2:使用NotificationCenter 现在,我们支持一对多通信,但是我们将线路数量增加了一倍,对吗? […]

有效的iOS错误管理

曾几何时,王国中有一个很棒的App。 该应用程序非常复杂,并包含许多晦涩难懂的部分,但它给使用它的所有用户带来了极大的欢乐。 但是,有一天,一个邪恶的错误出现了,并向王国的App用户显示了一个难看且难以理解的错误代码。 一位勇敢的开发人员被要求杀死龙来修复该错误,并且得到的票差不多是这样的: “用户无法继续购买-有时会出现一个奇怪的错误,有时什么也没有发生。” 勇敢的开发者拿起他的键盘,然后…忽略了该错误,删除了整个代码库,并重新编写了Swift 5中的所有内容。 再也没有看到错误代码,从此以后他们都过着幸福的生活。 结束。 不幸的是,现实生活中没有这样的童话。 存在错误,作为开发人员,您的任务是了解问题并加以解决。 现在,让我们看一下最常见的问题和iOS应用程序中通常如何处理错误的不足之处。 当应用失败并没有任何反应时,这可能意味着某个时刻丢失了一个或多个错误。 例如: 虽然这种方法的一些负面结果可能看起来很明显,但其他一些则更加模棱两可。 显而易见的是:用户屏幕上出现难看且难以理解的错误消息 这些错误消息显然不是针对用户的。 这些对用户可见的唯一结果是在App Store中有许多不良评论。 模棱两可:重要信息的丢失 相同的低级(网络/解析/核心数据)错误可能会以多种不同方式影响您的应用。 如果仅传播原始错误,则介于两者之间的所有信息都将丢失,不被记录或跟踪,最终调试该错误将变得更加困难。 对于用户而言,这些不希望的结果以及代码的完整性可以并且应该避免。 或者,如何以标准且一致的方式传播错误。 错误管理始终是任何应用程序的重要组成部分,甚至更重要的是,如果您的应用程序分为内部Pod,开发Pod,开放源Pod,库,实用程序和内部框架。 在整篇文章中,我们将这些依赖项称为“ 模块 ”。 在Just Eat,我们探索了Swift / Objective-C代码库的几种选择。 最后,我们选择继续使用经典的常绿NSError和NSUnderlyingErrorKey Universe,这是一种特别在macOS中使用的方法,自平台诞生之初就由Apple提出。 在介绍建议的解决方案之前,让我们先介绍几个关键概念。 如官方文档和NSHipster在本文中所述,NSError对象由以下内容组成: 错误域 -表示“错误上下文”或错误来源的字符串。 错误代码 -表示错误的Int值。 错误创建者有责任使其有意义。 不同的域可以具有完全不同的含义的相同错误代码。 用户信息字典 -包含有关错误的所有其他有用信息的字典。 错误链是NSError的链接列表。 每个错误都使用标准键NSUnderlyingErrorKey将先前的错误嵌入到用户信息字典中。 您可以将其视为标准链接列表。 如下所示。 我们汇总了一组准则,以统一我们在所有应用程序的各个部分,模块和库中进行错误管理的方法。 最终结果是一个一致的代码库 传播 展示架 日志 应用中生成的任何错误。 这意味着从应用程序用户界面收到的最终错误结构如下所示: […]

选择移动架构的重要性

介绍 多年以来,移动应用程序的开发并没有在良好体系结构的设计上大放异彩。 可能是由于移动应用程序(主要针对有限的设备,例如PalmOS增强型设备或WindowsCE),并且由于这些设备的功能和程序如此有限,因此它们运行在很简单的地方,就像史蒂夫·乔布斯所称的“玩具应用程序”一样,需求很高,并专注于正确地构建应用程序架构。 那是用于银行系统或大型应用程序,而不是用于移动应用程序。 现在,情况发生了巨大变化。 我们希望从移动应用程序中获得比以往更多的方式:我们使用几次聊天,发送各种消息,共享和捕获图像和视频,还编辑音频和视频,所有这些都在“简单”的小手机中进行。 也许移动应用程序不是完全成熟的桌面应用程序(也不是必需的),但是它们现在能够执行许多有趣的事情,因此开发此应用程序已成为越来越复杂的任务。 几年前,我在一家大公司工作,在那里,我们开发的应用程序之一(最初是一个简单的POC)最终成为了实际产品。 设计完全是一团糟:没有对象或交互的感觉,没有界面,也没有设计或体系结构。 启动它的人甚至都不了解最基本的iOS编程知识。 当然,此应用程序非常容易出错,添加功能确实是一项艰巨的任务。 没有人想碰它。 “要使用最小化工作量并最大化生产率的设计和架构来构建系统,您需要知道系统架构的哪个属性可以达到这个目的” 所以..我们做了什么? 好吧,我们要做的第一件事是开始弄清楚应用程序必须做什么,并开始确定必须创建哪些组件,并开始考虑它们之间的交互。 最后,过程大致上是这样的:1)创建一个POC,以实际查看应用程序是否可以执行客户期望的操作2)找到更好的组织应用程序组件并创建出色设计的方法允许对该软件进行单元测试3)实现此设计。 一些有趣的设计模式: 很高兴为我们完成了许多此类工作。 反复发现相同问题的非常聪明的开发人员提出了几个“设计模式”的思想,它们基本上解决了我们可能遇到的一些问题,这是: 设计一个面向UI的应用程序,该应用程序可以具有相互交互的单独层。一种使更改变得简单并具有整套单元测试的好处的方法。 MVC:模型-视图-控制器 MVC是一个非常有趣的移动体系结构。 它是1970年代由Palo Alto Research开发的第一个MV *体系结构,迄今为止,它是许多移动开发平台和许多Web开发框架中的核心体系结构。 今天,Apple将这种体系结构用作其示例和所显示代码的主要体系结构。 这种架构的思想是将视图与控制器分离,将模型与控制器分离。 视图 :视图负责向用户呈现UI元素,以及用户输入和中间件之间的交互。 视图对象知道如何显示,并且可能允许用户编辑应用程序模型中的数据。 该视图不应负责存储其显示的数据。 (当然,这并不意味着视图从不实际存储其显示的数据。出于性能原因,视图可以缓存数据或执行类似的操作)。 ViewController :控制器对象充当应用程序的视图对象及其模型对象之间的中介。 控制器通常负责确保视图可以访问他们需要显示的模型对象,并充当视图了解模型更改的渠道。 控制器对象还可以为应用程序执行设置和协调任务,并管理其他对象的生命周期。 Model :这是应用程序用来表示数据并在ViewController和服务之间移动信息的抽象。 模型对象代表特殊的知识和专长。 它们保存应用程序的数据,并定义处理该数据的逻辑。 设计良好的MVC应用程序将其所有重要数据封装在模型对象中。 [1] MVP:模型视图演示者 该体系结构由Microsoft的Martin Fowler于1997年发明。 该模式基本上包括3个可互操作的层。 “ 模型 ”与MVC或MVVM中的概念相同。 然后,“ Presenter ”是Model与ViewController或View Controllers与服务之间的中间层。 这是我们将要测试的层。 […]