Tag: mvvm

iOS中的MVVM —视图模型和网络

在上一篇文章中,我指出您的视图模型应尽可能精简。 这意味着您的视图模型不应执行数据访问或联网任务。 很少有读者指出,使用MVVM模式已久的Microsoft提倡通过视图模型调用网络和数据访问层的概念。 在本文中,我想采用Microsoft的方法,并在视图模型实现中移动网络代码。 我已经将ArticleListViewModel的定义更新为以下内容: ArticleListViewModel现在是一个类,并且需要初始化Webservice的实例。 因为现在在上一篇文章中使用过初始化器,所以现在忽略将其作为参数的初始化器。 ArticleListViewModel包含一个get函数,该函数负责将填充的视图模型返回给调用者。 而不是返回ArticleViewModel对象的列表,而是返回一个填充所有子对象的单个ArticleListViewModel对象。 现在,在视图控制器内部,您可以调用视图模型的get方法,如下所示: 调用域对象并将其转换为相应的视图模型对象的复杂性从视图控制器转移到视图模型实现。 如果您还记得我最初对视图模型的定义,则视图模型负责将数据提供给视图。 这种新方法当然可以做到这一点,但要以使视图模型复杂化为代价。 没有正确或错误的方法。 MVVM具有不同的形状,形式和实现。 在进行项目时,请采用最适合您需求的方法。

iOS中的测试驱动开发,SWIFT 4-第1部分

测试驱动开发(TDD),一种敏捷的软件开发方法,通过为我们的业务逻辑单元编写测试用例,可以使开发过程更无错误,并使代码更稳定。 如果您是TDD的新手,并且想稍微进一点,那么您想查看一下我先前关于测试驱动开发(TDD)的博客。 在iOS中,Xcode使单元测试比任何其他现代编程工具都容易得多。 让我们立即跳入编码,开始我们的冒险。 我们将使用MVVM构建我们的项目,因为它更易于进行基于组件的测试,因为我们对测试我们的业务逻辑而非控制器更感兴趣。 如果您不熟悉MVVM,那么您想快速浏览一下我关于MVVM的博客系列。 我们将学到什么? 我们将不仅致力于学习如何使用Xcode在iOS中编写测试用例,还将关注如何思考和计划我们的测试用例。 在Xcode项目中启用UnitTestCase 在新项目中启用。 添加现有项目。 ->要在现有项目中添加单元测试用例,我们需要添加一个新的iOS Unit Testing Bundle目标。 我们要建造什么? 我们将构建一个示例应用程序,该应用程序将获取场所(如商业场所和商业场所)的列表并显示它们。 因为我希望此博客非常简单,所以将从.json文件中获取位置列表。 让我们检查将用于DemoTests项目的UML类图。 下载 用于初始项目设置 的 启动程序项目, 即UT_Starter1。 入门项目具有上述屏幕的view , view模型和相应的测试文件。 作为#Rule1,我们需要编写有关Red-> Green-> Refactor流程的测试用例。 由于我们已经准备好编写测试用例,因此在Xcode中创建任何单元测试类时,都会生成一些自动生成的方法。 让我们继续学习该类中列出的所有方法: func setUp() 这是我们得到的最简单的Red案例。 因此,任何错误,即使是“未解决的标识符”也被视为红色案例。 绿色: 我们将编写最简单的代码来通过我们的测试用例。 导航到Place.swift 。 将以下代码放置在文件中。 在@testable import DemoTests下面写下@testable import DemoTests PlaceTests.swift 。 现在再次运行。 有用!! 您将在文件中看到绿色的勾号。 这意味着我们的代码已通过测试用例。 因此,我们已经成功地写下了绿色代码。 重构: 到目前为止,代码中没有太多可重构的,我们可以跳过这一部分。 […]

在iOS中查看模型验证

在上一篇文章中,我谈到了如何为iOS实现MVVM体系结构。 在这篇文章中,我将说明在将MVVM体系结构用于iOS应用程序时如何执行验证。 我们将在允许用户设置新密码的屏幕上工作。 下面的屏幕快照显示了屏幕的用户界面。 更改密码屏幕由“ ChangePasswordViewModel ”控制,该实现如下: 这是我们需要考虑的一些验证规则。 密码长度至少为8个字符 密码需要匹配 ChangePasswordTableViewController利用ChangePasswordViewModel为视图提供数据。 从文本字段到视图模型属性的方向绑定是在视图控制器内部设置的,如下所示: 每当您更改文本字段中的值时,视图模型就会自动更新,其中包含文本字段中的最新值。 现在,我们需要执行实际验证。 可以在保存操作内部的视图控制器中实现此操作,如下所示: 不要那样做! 您不希望视图控制器中包含验证逻辑。 这使得视图控制器的代码本来就不需要在这里膨胀。 解决此问题的一种方法是允许视图模型进行自我验证。 理想情况下,您将实现一个验证引擎类,其职责是验证视图模型。 验证引擎是自定义属性的偶像用例。 不幸的是,Swift当前不支持自定义属性。 每个坏规则将由以下实现的BrokenRule类表示: ViewModel协议定义协议,该协议由brokenRules集合和isValid布尔属性组成。 ChangePasswordViewModel符合ViewModel协议并实现validate函数,如下所示: 验证功能执行所有验证。 同样,最好有一个单独的验证引擎类来执行所有与验证有关的任务。 isValid属性将重置规则集合,然后调用私有验证功能。 最后,它取决于破碎规则列表是否包含任何破坏的规则而返回true或false。 现在,视图控制器可以使用以下代码来验证视图模型: 让我们测试一下。 运行该应用程序,然后为新密码输入以下值并确认密码,然后尝试保存/更新密码。 新密码=保留空白 确认密码=保留空白 视图模型将无效,您将在输出窗口上看到以下消息。 [Headlines.BrokenRule(propertyName:“ newPassword”,消息:“ Password应该至少包含8个字符”)) 新密码= password123 确认密码= password345 [Headlines.BrokenRule(属性名称:“ confirmPassword”,消息:“密码不匹配”))] 我确实希望在Swift的未来版本中可以实现自定义属性。 使用自定义属性,我们的视图模型将如下所示简单: 显然,您仍然需要为“ Required”属性创建实现,但是我相信它将从视图模型中删除很多不必要的代码。 所有验证逻辑将移至ValidationEngine类,该类将根据修饰后的属性动态验证视图模型,并返回中断规则列表。 编程愉快! 您可以在此处下载代码: azamsharp / HeadlinesMVVM 通过在GitHub上创建一个帐户为HeadlinesMVVM开发做出贡献。 […]

MVC替代品[第1部分:MVVM]

Model View Controller是我在iOS开发中学习的第一个概念。 您可以在iOS应用中看到最常用的设计模式。 苹果公司建议,它易于使用,并且相当容易理解。 如果您已经注意到有许多其他架构可以替代MVC。 在这里,我们将看到一个实用的列表,更重要的是这个问题的答案。 为什么MVC需要替代方案? 在MVC中,模型代表为该应用程序指定的数据模型。 视图是用户看到的应用程序的一部分。 控制器充当视图对象及其模型对象之间的中介。 它负责使模型相对于用户与View的交互保持更新,并负责通过Model的更改来更新View。 View会通知控制器任何用户交互。 然后,视图控制器更新模型以反映状态的变化。 按照定义,它们听起来是分开的,但实际上View和controller是紧密耦合的,而且每个人都知道另外两个。 这是所有问题开始的地方: 这违反了单一责任原则 这导致了一个普遍的问题,即所谓的Massive View Controller 这降低了每个实体的可重用性 代码的可测试性几乎是不可能的 在现实生活中,视图和模型是分开的,但是视图和控制器是紧密耦合的。 控制器和视图的所有代码都写在ViewController中。 View Controller可以快速将代码打包以用于各种目的。 这个问题称为Massive View Controller 。 Massive View Controller处于一种状态,许多逻辑和职责(例如网络连接,将Date转换为String和图像处理代码)已移入View Controllers。 这种做法会使您的代码庞大且难以更改。 它们显然不属于这里,但是我们应该将它们视为模型的一部分吗? 没有! 他们当然不是观点。 他们还能在哪里? 模型视图ViewModel在View和Model之间引入了另一个组件ViewModel。 该特殊层的主要目的是将数据状态从视图控制器移动到ViewModel(MVVM将视图和控制器都视为View ,并将视图控制器视为View)。 出于几个原因,MVVM成为了流行的iOS体系结构。 它着重于将用户界面与其他层分开。 MVVM与您现有的MVC架构兼容。 这种分离使应用程序更具可测试性,因为ViewModel对View一无所知。 通过使用MVVM,您可以将整个业务逻辑与曾经在MVC中耦合的UIKit分开。 视图模型 ViewModel是UIKit独立的视图及其状态的表示。 它从视图接收用户交互,从模型中获取数据,然后将数据处理为一组准备显示的属性。 这些属性中的每一个都代表视图中的UI组件。 有关为UI准备数据的所有逻辑(例如,将日期转换为字符串)发生在ViewModel中,而不是View中。 因此,可以为这些逻辑编写更简单的测试,而无需了解View的实现。 在MVVM中,没有直接在View中进行任何更改,我们在ViewModel中处理业务逻辑,因此View也会相应更改。 在观察ViewModel的更改后,View会自动更新。 […]

UIKIT APP的基础知识(MVC和MVVM)

这适用于iOS App开发的初学者或各个阶段的所有开发人员,他们真的想回顾一下基础知识。 如今,所有iOS开发人员都在努力将其应用程序设计转变为MVVM设计模式,但是所有开发人员都知道为什么这样做吗? 还是您中的一员,谁正在更改模式,因为您的高级开发人员想要更改。 我会解释“ 为什么 ”,不用担心don 首先,我想让您知道UIKIT应用程序基于MVC(模型-视图-控制器)设计模式。 模型 –就像是您的应用程序的宝贵资源,就像您想要在应用程序上显示的数据,其应用程序的数据以及业务逻辑一样,可以帮助将数据修改为应用程序中更具表现力的方式。 视图 –主要处理用户界面,它是数据的可视界面。 控制器 –就像模型和视图之间的桥梁一样,它在适当的时候从模型和视图之间移动数据,反之亦然。 请查看下面由Apple提供的有关MVC设计模式如何工作的图像。 在了解了MVC的知识之后,让我们开始MVVM。 我将以最简单的方式来描述MVVM,MVVM只是MODEL-VIEW-VIEWMODEL。 在这种情况下, Viewmodel充当View和Model之间的桥梁。 在大多数情况下,业务逻辑也从Model插入,并在ViewModel中完成。 其次,在这种情况下, View仅用于发布内容,它仅显示分配给它及其子视图的内容,应显示的内容以及应如何显示的内容再次由ViewModel处理。 看起来ViewModel是此设计模式中的主角。 那么,为什么许多iOS开发人员更喜欢MVVM而不是UIKIT的MVC? 当涉及到Xcode Project时,当您尝试在UIKIT APP中创建ViewController时,将以如下所示的形式创建它: 您可以看到Controller是用自己的View创建的,它们就像密不可分,因此,如果您要对Controller进行单元测试,它实际上涉及View,那么模拟ViewController非常困难,并且您需要做很多事情不知道ViewController会发生这种情况,而我们实际上没有任何控制权。 最后,您实际上没有机会独立测试实际的业务逻辑。 在MVVM设计模式中,您实际上可以独立地测试业务逻辑,可以将业务逻辑与模型分离,可以分离数据的转换方式,以便在View中显示,并且模拟ViewModel真的很容易。 如何将给定的UIKIT的MVC设计转换为MVVM,请查看以下图像: 至 因此,为了将MVC更改为MVVM,您需要一个额外的文件,例如:ViewModel.swift并将所有逻辑拉入其中,只是将整个ViewController视为MVVM。 因此,在这里,ViewController的部分是发布或显示数据和视图。 如果ViewController(在MVVM中为视图)要发布某些内容,则它应该来自ViewModel,后者再处理来自Model的数据以将其提供给ViewController。 如果用户想要更新其数据,则用户将更新屏幕上的数据,并将该信息发送到ViewModel,后者将更新模型。 那是最终读者,希望大家喜欢这篇文章。 如果您有任何疑问或想念任何内容,请在💁下方评论。 朱丽叶编码器签字……

Swift中的项目架构MVVM

如果您一直在使用Model-View-Controller模式来构建Swift应用程序,这可能看起来很熟悉。 它代表Model-View-ViewModel 问题: 让我们从一个例子开始: 在上述程序中, MovieViewController完成加载其视图后,将执行网络请求。 它将此任务委托给MobileAPIService类,这是减少视图控制器职责的良好起点。 实现看起来不太复杂,这可能是正确的。 它有一些缺陷。 但是我们关心的是视图控制器为何负责发出网络请求?🤔确实, MobileAPIService可以帮助MovieViewController完成此任务,但是MovieViewController仍然知道它获取的电影来自后端。 您可能会争辩说,这些细节被MobileAPIService隐藏了,但这不是重点。 对于MovieViewController应该考虑以下几点 。 MovieViewController控制一个视图。 除了API调用,本地数据库处理等其他处理外,它仅应用于UI呈现。 它不应该知道电影从哪里来。 它只需要询问要在其视图中显示的内容。 它甚至不应该了解MobileAPIService。 更不用说它是如何工作的。 在客户端程序(我们的案例MovieViewController )中,必须有最少的代码,从而减少了错误。 不幸的是,我们没有在程序1.0中实现以上几点。 解决方案: MVVM模式是MVC模式的稍微复杂一些的实现,请花一点时间来思考一下MVVM在上述实现中可能具有的优势。 与程序1.1相比,在程序1.0中的实现是如此简单,这可能是正确的。 MVC实现的ViewController还可以管理状态,newsList实例,MobileAPIService等。 这不是坏事还是好事,但是在MVVM中,它在ViewMode中隐藏了这些细节 MVVM实施 的 新变化: MVVM模式在ViewModel中隐藏电影,状态, MobileAPIService详细信息。 发现ViewController仅负责UI控件。 ViewController与一系列电影无关。 甚至不知道电影的来源,例如通过远程本地存储。 它们可能来自由核心数据SQLite管理的后端或本地存储。 单元测试和可测试性得到改善。 它将这些细节隐藏在ViewModel中 是什么使Model-View-ViewModel很棒 体验MVVM的第一个好处是可以从视图控制器中删除多少代码。 视图模型是位于模型层和控制器层之间的轻型对象。 您的视图控制器不会直接与应用程序的模型层通信。 视图模型为视图控制器提供数据,并提供与模型层交互的界面。 单元测试 通过移动业务逻辑(数据操纵)来查看模型。 测试我们的应用程序更加容易。 测试视图模型很容易,因为它不包含客户端程序引用。 视图控制器不依赖于模型层,因此易于测试。 任务分离 通过添加视图模型,视图模型将数据层的数据转换为视图使用的某种内容(例如模型)。 视图控制器不负责此任务管理。 请找到完整的源代码 shantaramk / […]

使用RxSwift重构为MVVM

当您查看代码并几乎不了解它应该做什么或它需要这么多行时,每个开发人员都会意识到这一点。 这并不意味着您必须完全从头开始,但是对重构代码的一部分开放很重要。 重构的目的是使您的代码更易于理解,测试和/或更新。 这是许多文章的第一篇,我将分享我的应用程序We To Too的重构过程,以及沿途学习的模式,库和工具。 四年前,当我第一次开始学习iOS开发时,我学到的设计模式是MVC,它代表Model View Controller。 下图以MVC模式显示了用户交互: 如果您正在学习iOS并依赖Apple的文档,您会注意到它们遵循MVC模式。 随着时间的推移,随着应用程序的增长,很容易将应用程序的所有逻辑写入视图控制器,从而导致难以测试和理解的超大型类(通常称为Massive View Controller)。 MVVM是最近流行的另一种设计模式,它代表Model-View-ViewModel。 视图模型允许业务逻辑与视图控制器分离。 这样,我们的视图控制器就变得很小,只专注于UI。 视图模型通常不导入UIKit,并且不了解视图或视图控制器。 这使视图模型可以进行单元测试。 该模型通常是表示数据的结构或简单类。 遵循MVVM模式并不意味着您的代码将是完美的。 正如您可以拥有大量的视图控制器一样,您也可以拥有大量的视图模型。 您不必在整个应用程序中使用一种设计模式,这取决于哪种功能最适合您要实现的功能。 在上面的MVVM图中,有诸如“数据和用户操作绑定”,“更新”和“通知”之类的术语。 您如何实际在MVVM中实现这些关系? 许多开发人员通过反应式编程为这些关系建模。 响应式编程是软件开发中已经存在了数十年的范例,它使开发人员可以编写专注于异步数据流的声明性代码。 查看Andre Staltz撰写的这份出色的文档,其中有更多解释。 响应式扩展(Rx)是响应式编程如何特别是在移动开发中普及的方式。 为什么我要学习Rx? 用户期望现代应用程序能够响应。 将所有内容都视为带有输入和输出的流,可以确保始终对发生的任何更改(无论是用户操作还是网络请求)做出反应。 响应式编程使用观察者设计模式,订户“侦听”流(可观察)。 有关反应式编程和RxSwift的更多信息,请观看Shai Mishali的精彩演讲或阅读文档。 我之前写过关于可测试视图模型的信息,但是我们将如何测试它们呢? 我决定使用John Shore创建的Red green重构方法。 这样可以确保在编写代码时遵循TDD思维方式。 在逻辑完成之前编写测试, 然后编写逻辑以符合测试要求。 测试通过后,您将找到无需破坏测试即可改进代码的方法。 Kickstarter在其代码库中遵循此方法,您可以观看此视频以了解其工作原理。 他们使用ReactiveSwift并编写了自己的测试助手。 我正在使用RxSwift,发现了一个非常有用的Cocoapods库,称为RxExpect。 现在,我们已经为理解MVVM和反应式编程的基础奠定了基础,让我们开始重构我的应用程序We Read Too的重构,该应用程序全部用Swift编写。 我们也阅读是一款具有以下功能的图书目录应用程序: 集合视图显示按年龄类别过滤的书籍 按作者,标题或描述搜索 详细信息视图,显示书的详细信息以及从Goodreads提取的数据,并允许用户共享书或在Safari / […]

RxSwift + MVVM,一次一点

我很幸运能够参加尝试! Swift Swift纽约今年,我观看了许多有趣的演讲。 我最喜欢的是Paul Fenwick对机器伦理的预测,以及Carl Brown对Swift中常见错误的有趣分析。 我强烈建议您关注这些家伙,听听他们的意见。 但是,确实发生了两件事情,这使我对新思想产生了开放的想法,从而导致了我日常工作中急需的改变。 第一个是Ash Furrow主持的RxSwift研讨会,第二个是Nataliya Patsovska的MVVM演讲。 本文假定您具有响应式和函数式编程的一些基本知识,但希望所有开发人员都可以一定程度地访问它们。 每天,我在Match的BLK应用程序上工作,在该应用程序中,我使用RxSwift的几率极低。 直到最近,我的使用仍主要限于网络代码和模型转换。 我知道我没有充分利用框架的潜力,但是由于某种原因,我仍然故意不了解一些我现在认为使其真正强大的核心功能。 Ash帮助我发现了其中一些功能,现在我感到有能力以更具反应性的风格编写代码。 但是,在查看新代码之前,我认为查看一些旧代码对理解我的问题很重要: 这是与我的应用程序中其他序列相似的Observable序列的示例。 我几乎所有的RxSwift代码都看起来像这样。 在这里,我要从IBAction函数调用fetchUsers() ,在其中创建一个新的Observable(API请求),转换所得数据,然后在序列上调用订阅 。 为了简洁起见,此代码示例和后续代码示例中已省略一些代码。 我将其留给读者来推断(或简单地忽略)此链中某些函数的实现细节,例如mapModel(model:) 。 您可能还注意到此功能包含副作用。 具体来说,我正在显示/隐藏自定义加载视图,并重新加载表视图。 最后,我有两个“配置”和处理错误情况的功能。 这是声明式外观背后的命令式编程。 我对RxSwift还是很陌生,但是现在我已经学会了将这类订阅视为一种代码味道。 让我们从列举此代码的潜在问题开始: 函数fetchUsers()势在必行。 每次要获取用户时,IBAction必须调用此函数。 如果将按钮轻击表示为事件的可观察流,那么将此流绑定到获取程序会更好吗? 可观察对象具有一个内置函数,用于处理称为do的副作用。 我们可以使用此功能来显示/隐藏加载视图,但是如果此Observable序列根本不关心UI,会更好吗? 在很多情况下,在Observable上调用订阅是很有意义的,但这可能不是其中一种。 此序列的最终目的不是执行某些副作用,而是获得一个具体的模型:一组用户。 将此Observable 绑定到需要数据的Observer有意义吗?

MVVM绑定

绑定您的ViewModel 如果您曾经尝试为iOS应用程序自行实现MVVM,则可能会难以通过Controller竞标ViewModel。 我正在尝试使用Closure简化本文前面的过程。 本教程使用xCode 10.1和Swift 4.2 步骤1:创建一个单一视图应用程序并创建组,如下图所示。(没有严格的规则来排列文件,您可以根据自己的选择进行结构化) 步骤2:为每个组创建快速文件,如下图所示。 (稳定和支持小组将拥有项目资源) 第三步:我们将以编程方式添加✌️textFields和☝️按钮。 您可以在此处找到代码。 步骤4:我们将为LoginViewModel添加代码。 LoginViewModel的主要职责是跟踪我们的视图所呈现的内容。 步骤5:尽管在此示例项目中我们尚未使用Model,但是您可以相应地使用它。

追踪RxSwift内存泄漏

这是关于使用Xcode内存图调试器来跟踪和消除RxSwift中的内存泄漏的简短教程。 正如我在前一篇关于避免RxSwift中的内存泄漏的帖子(https://medium.com/@chuck.krutsinger/avoiding-rxswift-memory-leaks-87885bd0023d)中提到的那样,我介绍了一些无意中造成内存泄漏的方法。 在本文中,我将跟踪一些内存泄漏,以演示如何使用Xcode和RxSwift中可用的工具。 样例项目 您可以从MemoryLeaksExample下载示例项目。 该示例应用程序在MVVM架构中使用RxSwift / RxCocoa与ReSwift结合使用,以隔离有状态的副作用。 我不会在这里介绍这些架构模式,但会在以后的文章中介绍。 仅供参考-示例项目是使用Xcode 10.1,Swift 4.2,RxSwift 4.4.0,RxCocoa 4.4.0和ReSwift 4.0.1编写的。 该调试会话将重点放在MemoryLeakViewController和MemoryLeakViewModel上,它们的功能与PlayGamesViewController和PlayGamesViewModel相同,但是会发生内存泄漏。 我不建议将MemoryLeak *版本用作开发风格,因为这种风格可能会导致我们在本文中将要介绍的各种内存泄漏。 其他视图控制器和视图模型是更好地构建MVVM解决方案的更好示例。 在以后的文章中,我将讨论并演示以这种样式的MVVM创建内存泄漏有多么困难。 入门-泄漏 下载项目后,您必须将MemoryLeaksExample目标-> General-> Team设置更改为您的团队,关闭工作区,然后安装Cocoapods。 项目运行后,请在Xcode中观察调试区域,以查看RxSwift资源计数。 登录屏幕上的资源计数为205。 您可以使用除“假”之外的任何用户名和密码字符串登录,这将导致登录失败。 继续登录以进入主屏幕。 在调试区域中,您将看到资源数量减少了,并且登录屏幕已被释放。 主屏幕使用的RxSwift资源少于登录屏幕,因此减少的计数确认登录屏幕在分配时释放了其资源。 接下来,通过点击“ Memory Leak Example”,然后点击“ Home”后退按钮,演示内存泄漏。 这样做几次。 您会在调试区域中看到,没有来自MemoryLeakViewController的deinit消息,并且资源数量稳定增长。 不好。 我们将对其进行跟踪。 内存图调试器 在使用内存图调试器之前,我们应该启用malloc日志记录,以便可以跟踪泄漏。 编辑构建方案,转到“运行”选项卡,然后选择“诊断”选项并启用malloc日志记录。 在主屏幕上时,通过点击Xcode中的按钮来启动内存图调试器。 然后,您将看到存在MemoryLeakViewController和MemoryLeakViewModel的多个实例,每次打开该视图并返回到主屏幕时,每个实例一个。 以我为例,它已经执行了3次,这是三个实例。 您还会注意到,我们泄漏了GameCell实例,因为每个视图都打开了其中的10个,另外还有一个由表视图创建的实例。 展开MemoryLeakViewController,单击一个实例,然后查看该图。 视图控制器实例在最右边,您可以看到从RxTableViewDataSourceProxy到视图控制器实例的一系列引用。 单击最靠近视图控制器框的Swift闭合上下文框。 现在打开右侧的Xcode面板以查看回溯。 嗯,没有提及我们的视图控制器。 尝试左边的下一个。 这确实提到了我们的视图控制器。 如果将鼠标移至回溯中的该项目,则右侧会出现一个小箭头。 单击该箭头,您将进入创建该闭包的代码。 […]