你好! 我是亚历克西斯。 我目前正在学习使用Swift编写iOS应用程序代码。 要学习Swift语言,我正在跟踪一些Udemy的介绍。 我的长期目标是能够成为一名应用程序开发人员。 不一定只有iOS应用程序,这只是一个起点。 我是妈妈,妻子,而且我全职工作,因此显然有时候可能会有些挑战。 有时候,我没有动力,因此对从事这个职业感到很兴奋。 我感觉自己在家中工作,拥有数百种成功的应用程序,可以清晰地看到我的未来。 其他时候,我非常沮丧。 感觉就像在1,000,000英里之外。 仅仅能够开发一个应用程序所花费的时间和精力的步骤有时似乎是极其巨大的。 但是最终,我内心深处知道我打算这样做。 我知道这一点是因为无论我感到多么害怕,不值得,或者绝对确定自己将要失败,我都不想放弃。 我愿意为此失败。 我知道对于大多数人来说这听起来很奇怪。 但是任何成功的人都知道失败是成功的重要组成部分。 我愿意制作100个失败的应用程序,而只是制作一个成功的应用程序。 这就是我知道我注定要成为应用程序开发人员的方式。 不要误会我的意思。 失败的想法吓坏了我,但我学会了生活在爱与信任的地方,而不是恐惧的地方。 因此,我决定创建一个博客,跟踪我的进度,追究我的责任,并保持动力。 我的结论真的很糟糕。 凯再见👋🏽
让我们在本教程中说明“测试驱动开发”。著名的TDD示例之一是保龄球游戏,它的游戏规则涉及真正需要单元测试的一对逻辑。 保龄球游戏有以下四个规则,我们的单元测试就是基于此。 游戏包括十帧,每位玩家都有两次机会在每一帧中敲钉子。 备用:如果玩家在同一帧中两次尝试敲10个针,此当前帧将获得奖励分数。 奖金是下一次玩家掷出的敲击针的数量,例如,第三帧获得奖金5,导致第三帧获得罢工奖金,我在下面做一些注释,红色注释表示备用奖金。 打击:如果玩家在第一次尝试中击中10个针脚,当前帧将获得打击加成,这将增加接下来两轮的得分,例如,第五帧将获得打击加成,橙色以下表示打击加成。 在第十帧中滚动“备用”或“罢工”的玩家将获得额外的加值,以再次滚动,但在第十帧中滚动不超过三倍,因为蓝色六分以下是此奖金。 让我们开始编码,启动Xcode创建一个新项目,别忘了单击“ Include Unit Tests”复选框。 步骤0:创建一个类游戏,以处理保龄球游戏逻辑。 打开TDD_BowlingGameTests.swift,此文件是我们编写单元测试的地方,每个函数必须以“ test”为标题才能运行单元测试。 在TDD中,如果结果失败,则必须编写单元测试并在开发之前对其进行测试,然后开始修改功能,直到通过单元测试。 步骤1:一名玩家掷出20次却没有敲门,预期得分为零。 显然,保龄球游戏必须具有函数“ roll”和“ score”,才能掷出保龄球并计算得分,因此我们编写了单元测试,第一个测试条件是玩家掷出20次并不敲任何东西,预期分数将为零。 //第29行:如果game.score()== 0为true,则通过单元测试。 XCTAssertEqual(game.score(),0) 当然,结果失败了,让我们修改Game中的功能。 让score()返回0怎么样。 好了,首先进行单元测试是为了测试玩家掷出20个球而不敲任何针脚的情况,经过修改的功能通过单元测试后,我们应该对其进行git commit。 git commit -am“传递20个球而不会敲任何针” 步骤2:一名玩家掷出20个球,每滚动2个针,预期得分为40。 通过第一个测试条件后,我们可以将另一个条件写为第32〜38行,该条件将是敲2个引脚,每次滚动然后运行测试。 如您所见,我们失败了,现在我们应该修改函数,添加一个属性来存储得分会有所帮助。 好的,我们的Game类可以在修改后通过第二个条件,同时通过第一个条件,我们应该对其进行git commit。 这是编写单元测试的好处,无论您进行了什么修改,都可以对其进行测试,并确保立即没有错。 git commit -am“传递敲击每个滚动2个针” 第三步:编写干净的代码 正如您在测试用例中看到的那样,第25、33行创建了游戏副本的实例,第26、34行中的for-in循环也被复制,这是不好的编码风格,让我们对其进行修改并再次运行测试。 修改代码使其更整洁,并且单元测试无误通过后,我们应该对其进行git commit。 git commit -am“优化代码” 步骤4:玩家获得备用奖金 让我们测试一个玩家掷出1,4,5,5,6,0…的分数必须如下计算:第1帧(1 + 4),第2帧(5 + 5 + 6),第3帧(6 +0),因此总分必须为27,才能在第二帧获得备用奖金。 显然,我们遇到了失败,我们的游戏逻辑未处理备用条件,让我们对其进行了修改,因为我们需要考虑备用条件,因此由于总得分不再起作用,因此使用商店属性totalScore来计算总引脚数,我们需要的是数组以重新编码玩家滚动的每个掷骰,并处理备用条件,因此我们如下修改了Game。 […]
简单的5点指导可将您的单元测试带入一个全新的水平。 总体来说,Swift世界最近发生了很多事情,这种趋势并没有错过对开发的一部分进行测试的机会。 尽管良好的测试实践正在传播,但对旨在帮助该过程的工具的需求也在增长。 我很高兴成为一个团队的成员之一,该团队致力于开发一种名为SwiftyMocky的解决方案。 1.模拟是测试双 一般而言,在谈论单元测试和测试时,有一整套特殊目的的对象,称为“测试双打”。 在本文中,我将仅简要介绍它们,因为需要理解SwiftyMocky背后的整个概念。 Test Doubles背后的故事很简单。 由于缺乏更好的词汇,经典的测试方法通常依赖于状态验证。 我们为sut及其依赖项创建初始状态,然后验证执行测试后的状态是否符合我们的期望。 尽管如此,并不是在每个测试用例中我们都在测试sut ,我们可以使用实际的实现作为其依赖项。 数据库操作或网络调用就是一个很好的例子。 我们真的不想在这里进行真正的通话,因此sut尝试进行的了解通常足以确保有效性。 另一个很好的例子(来自Martin Fowler的帖子)是发送电子邮件-很难从测试角度进行验证。 这就是我们的特殊情况对象出现的地方。 基于Gerard Meszaros提出的词汇(我个人觉得非常有用),我将其分为以下几类: 虚拟 :完全没有实现,该对象的整体唯一目的是满足依赖关系要求。 伪造的 :简单但有效的实现,通常采用捷径。 例如,伪存储将宁愿使用某些内存数据结构(如数组或字典),而不是包装核心数据操作。 在提供与具体实现相同的功能的同时,它使状态验证更加容易。 存根 :包含预定义的响应和答案,通常严格针对特定测试用例定义。 Spy :存根,不仅返回预定义的答案,还记录调用的方法。 模拟 :预先设定的期望值,可以验证sut的行为是否符合预期。 在本文的其余部分,我们将重点介绍Mock ,因为它可以处理正确测试所需的大多数内容。 请注意,虽然上述单词在测试双精度类型之间严格区分,但是在大多数工具中, Mock 都是扩展的,具有 Stub 和 Spy的 组合功能 。 在 SwiftyMocky中, 我们执行相同的操作,因此,每当引用 Mock时 ,我们的意思是同时提供 Stub 和 Spy 功能的对象。 尽管拥有Mock对象的想法很明确,并且收益不能被夸大,但这给我们带来了一个问题。 必须编写模拟实现,如果有适当的层分离,则在大型项目中这可能会成为相当大的开销。 在具有适当反映的语言中,有许多库和框架可以在运行时创建Mock ,使您可以选择一种最适合您的测试风格的库。 […]
这就是我写一个简单工具的方式。3次,用不同的语言。 本实验 我的目标是编写一个非常简单的命令行工具,该工具可以生成与Google Authenticator兼容的一次性密码。 Google身份验证器使用基于时间的一次性密码算法(TOTP)生成代码。 我不想使用自己的实现,而是想使用现有的TOTP库,因为已经有很多不错的库。 本质上,我要我的工具要做的就是接受一个秘密作为单个输入,然后调用现有的TOTP库生成代码,并将生成的访问代码打印到标准输出中。 我问自己的问题是:假设我想在多个平台(Mac,Windows,Ubuntu)上使用该工具,并且希望 在少数人(不一定是技术人员)(例如同事)中分配工具,哪种编程语言将是最实用/可行/有趣的选择? 当然,您可以从多个角度看待这个问题。 让我们专注于构建和分发工具。 然后,这些是我的“应有”要求: 应该有可能将工具分发为“开箱即用”的单个可执行文件,这意味着用户不必安装运行时,框架,库等依赖项。 使用相同的代码库(但可能使用不同的工具链),应该可以为多个平台生成构建。 语言选择 我想为这个特定的实验创建一个二进制文件,这就是为什么我没有为此特定工具考虑诸如Node.js,Ruby和Python之类的解释语言的原因。 当然,尽管总的来说,这些语言都能为编写跨平台命令行工具提供完全可行的选择。 这些语言还有一个缺点,那就是最终用户需要安装运行时(例如Node.js)。 尽管许多平台都预先安装了常见的运行时,但是用户可能需要安装其他版本。 对于非技术用户而言,这并不总是一件琐碎的任务。 (我知道有一些工具可以将解释后的语言编译为独立的可执行文件,但这有点像在作弊) 。 最后,我的选择是尝试使用C , Go和Swift 。 我决定留在“编程语言舒适区”,因为学习一种新语言不是我实验的一部分。 因此,我没有尝试过(很有用)非常有趣的其他语言,例如Rust ,我将在以后尝试 (随时对您的Rust经验发表评论)。 也许还需要注意:在本实验中,我考虑了C ++的过大杀伤力(或者实际上,我的C ++ 只是缺乏知识)。 我学到的是 C 通常,使用C构建的可执行文件是动态链接的。 这意味着最终用户需要安装依赖项(链接的库)才能运行该工具。 那绝对不是理想的。 有很多解决方法,但是所有这些都有一些缺点: – 静态链接:创建单个二进制文件,其中将包含所有必需的二进制代码。 但是,这要求您使用的所有库(例如TOTP库)都支持静态链接。 绝对不是总是这样。 此外,Apple在Mac OS X上不支持静态链接的二进制文件。 – 将链接的动态库与您的应用程序一起分发 。 这意味着对于每个目标操作系统,您都必须预先构建所有链接的库,请确保可执行文件可以找到这些库(例如,在macOS上更改rpath),并将其与应用程序捆绑在一起。 换句话说,您需要将.dll (Windows) .dylib (macOS)或.so […]
正如大多数iOS开发人员所知,显示数据集是构建移动应用程序中的一项相当常见的任务。 Apple的SDK提供了两个组件来帮助执行这样的任务,而不必从头开始执行所有操作:表视图( UITableView )和集合视图( UICollectionView ) 。 表格视图和集合视图均旨在支持显示可滚动显示的数据集。 但是,当显示大量数据时,实现完美平滑的滚动可能非常棘手。 这不是理想的,因为它会对用户体验产生负面影响。 作为Capital One Mobile应用程序iOS开发团队的成员,我有机会对表格视图和集合视图进行了尝试。 这篇文章反映了我在显示大量可滚动数据方面的个人经验。 在其中,我们将介绍最重要的技巧,以优化上述SDK组件的性能。 此步骤对于获得非常流畅的滚动体验至关重要。 请注意,以下几点大多数都适用于UITableView和UICollectionView,因为它们共享大量的“ 幕后 ”行为。 关于UICollectionView的几点要点, 因为此视图将其他布局细节放在开发人员的肩膀上。 让我们从快速概述上述组件开始。 UITableView经过优化,可以将视图显示为一系列行。 由于布局是预定义的,因此SDK组件会处理大部分布局,并提供主要专注于显示单元内容的委托。 另一方面, UICollectionView提供了最大的灵活性,因为布局是完全可定制的。 但是,集合视图的灵活性是以必须注意有关如何执行布局的其他细节为代价的。 UITableView和UICollectionView的 共同技巧 注意: 我将对 代码段 使用 UITableView 。 但是相同的概念也适用于 UICollectionView 。 单元格渲染是一项关键任务 UITableView和UITableViewCell之间的主要交互可以通过以下事件来描述: 表格视图正在请求需要显示的单元格(tableView(_:cellForRowAt :))。 表格视图即将显示单元格(tableView(_:willDisplay:forRowAt :))。 该单元格已从表视图(tableView(_:didEndDisplaying:forRowAt :))中删除。 对于上述所有事件,表视图都将传递与其进行交互的索引(行)。 这是UITableViewCell生命周期的可视化: 首先,tableView(_:cellForRowAt :)方法应尽可能快。 每次需要显示单元格时都会调用此方法。 它执行得越快,表格视图的滚动就越平滑。 为了确保我们尽可能快地渲染单元,我们可以做一些事情。 以下是来自Apple文档的呈现单元格的基本代码: 在获取了将要重用的单元实例之后(dequeueReusableCell(withIdentifier:for […]
下一个场景 那就对了! 我们正在转移到另一个场景。 实际上,完成后,这将是我们应用程序的第一个屏幕。 创建新场景,将其放置在Scenes文件夹下,并将其命名为MenuScene 。 在MenuScene.swift文件中输入以下代码: 按钮状态! 在SpriteKit中,按钮可能比较棘手。 有很多可用的第三方选项(我甚至自己做了一个),但是从理论上讲,您只需要知道三种触摸方法即可: touchesBegan(_ touches:with event 🙂 touchesMoved(_ touchs:with event 🙂 touchesEnded(_ touches:with event 🙂 我们在更新保护伞时简要介绍了这一点,但是现在我们需要知道:触摸了哪个按钮; 我们是释放水龙头还是单击该按钮; 如果我们仍在触摸它。 这就是我们的selectedButton变量起作用的地方。 当我们开始触摸时,我们可以捕获使用该变量开始单击的按钮。 如果将其拖动到按钮之外,则可以对其进行处理并为其提供适当的纹理。 释放触摸时,我们可以查看是否仍在触摸按钮内部。 如果是,那么我们可以处理与其相关的动作。 将以下代码行添加到MenuScene.swift的底部: 采取行动了! 现在,我们已经掌握了基本的按钮行为,我们需要一个事件来触发它们的点击。 易于实现的按钮是startButton 。 单击时,我们只需要展示GameScene即可 。 将MenuScene.swift函数中的handleStartButtonClick()更新为以下代码: func handleStartButtonClick(){ let transition = SKTransition.reveal(with:.down,duration:0.75) 让gameScene = GameScene(size:size) gameScene.scaleMode = scaleMode view?.presentScene(gameScene,过渡:过渡) } 如果您现在运行该应用程序并按按钮,游戏将开始! 现在我们需要实现静音切换。 我们已经有一个声音管理器,但是现在我们需要能够告诉它是否静音。 […]
在此博客文章中,我将创建一个示例应用程序项目,并解释每个步骤以指导您最终了解如何使用CoreLocation获取位置信息。 在XCode中创建一个新的Single View Application项目。 第一步是将CoreLocation框架添加到您的项目中。 在本教程中,我们将创建一个专用类来处理所有CoreLocation内容。 该类名为LocaitonService 。 参见下面的代码,我们制作了LocationService类。 第一步是导入CoreLocation。 初始化位置管理器 下一步是在LocationService类中实例化LocationManager类。 LocationManager是CoreLocation框架的核心。 requestWhenInUseAuthorization向用户显示更多可接受的消息。 上面警报的消息正文可以(必须)在Info.plist中指定。 为Info.plist中的NSLocationWhenInUseUsageDescription键指定要显示的消息,如下所示。 获取位置 下一步是实现CLLocationManagerDelegate 。 获取位置信息是异步任务。 该应用要求CoreLocation获取GPS位置信息。 然后,CoreLocation中间件要求GPS驱动程序获取GPS位置信息。 一旦GPS芯片成功获取位置信息,它将回调GPS驱动程序,然后GPS驱动程序继续回调CoreLocation,最后CoreLocation调用CLLocationManagerDelegate的方法。 通过此过程,我们在CLLocationManagerDelegate的didUpdateLocations方法中异步接收位置。 使您的LocationService类采用CLLocationManagerDelegate协议(请参见下面的代码的第一行)。 运行应用程序,然后从模拟器菜单中选择City Run ,您将看到在didUpdateLocations方法中收到的大量位置,如下所示。 而已。 这是获取用户位置的所有基础知识。 这是此示例源代码的链接。 https://github.com/mizutori/iOSLocationStarterKit 由于此示例随着博客的发展而发展,因此请使用5b7f7fd5a54bdce0b10b978b97ae52391422dd6b的 SHA哈希检查提交,该提交仅包含此博客文章中介绍的代码。 下一篇文章—背景位置跟踪
您是否在使用长签名功能时遇到麻烦? 您认为重命名某些类型是个好主意吗? 好吧……只是看看 typealias 。 什么是Typealias? 我第一次发现有一种叫做“别名”的东西,对我来说有点奇怪,即使知道它的用途后,在我看来,使用它也没有“好”的意义。 到目前为止,我是它的粉丝! 在开始使用Swift typealias之前,我想指出的是,别名并不是 Swift编程语言的专有术语,实际上它是一个计算术语,您可以在许多其他古老和现代的语言中找到“别名”的逻辑(例如作为Swift)编程语言… Typealias是现有类型的命名别名,这意味着通过使用它,您将能够命名类型,以使程序更方便。 请记住,typealias 不是新类型,它只是现有类型的名称,您可以将其视为类型的替代名称(它可能是“快捷方式”,您将很快阅读)。 如何实现typealias? 简单来说,您可以将typealias实现为: typealias yourCustomName =现有类型 而这一切! 让我们开始做一些非常简单的代码: 实施: typealias Strings = [String] 可让您声明字符串数组( [String] )类型的变量(例如instance),如下所示: var strs: Strings? 现在strs将是[String]类型的变量。 非常有趣! 老实说,对我来说我不这么认为。 如果您想到的是“到目前为止,它是没有用的”,我会同意agree。 毫无疑问,为这种情况创建类型别名在某些情况下将其声明为var strs: [String]?并不是那么有用var strs: [String]? 读起来会更好。。。让它平静下来,让我们尝试另一种方法:将字符串字典的类型别名声明为键,将整数声明为值: typealias CustomDict =字典 在声明字典的泛型参数的具体类型( )时,应该节省一些“精力”,就像“双向2合1保存”! 但是,仍然不是使用typealias的充分理由,对吗? 这导致: 为什么要使用typealias? 实际上,通常在使用非平凡的应用程序时,它应该具有自己的管理器来处理指定功能的任务,例如通过网络连接发送和接收数据,获取设备照片(访问系统资源),从磁盘写入数据或从磁盘读取数据等等。因此,实现这些管理器可能需要为其方法使用“相当长”的签名。 让我们更进一步,为更多“真实世界”案例编写一些代码: 例: 为了使其更简单,我将重点介绍方法的签名,而不管它们的实际内部实现是什么(管理者目的)。 考虑我们有包含foo […]
大小类和自动布局可通过定义显示环境更改时屏幕,视图控制器和视图的布局应如何调整来帮助您满足渲染期望。 例如,Size类将帮助您定义当应用程序以横向放置在iPhone上时屏幕的外观,或者将应用程序显示在iPad或较小设备上时的外观。 我们可以为特定尺寸定义屏幕布局。 Size类只有两种类型的大小: 常规尺寸 o常规高度 o常规宽度 紧凑的尺寸 o紧凑的高度 o紧凑的宽度 我们拥有的所有iOS设备都属于上述类别。 此大小级别适用于设备,而不适用于它们具有的分辨率。 例如,所有处于纵向模式的iPhone都将具有“常规”高度和紧凑宽度。 让我们看一下网格以使其更容易。 现在,我们将看到来自Xcode的所有类别的屏幕截图 最佳做法是将屏幕设计为任意高度,任何宽度。 还要添加自动布局所需的所有约束,然后我们可以根据尺寸类别进行更改。 例如,如果我想在我的应用程序在iPhone上进入横向模式时删除标签栏,那么我将选择大小级别的紧凑高度和紧凑宽度。 然后,我将删除标签栏。 要从特定类中删除标签栏,我们需要使用“命令+删除”键盘快捷键,而不仅仅是“删除”。 这样,当您在iPhone上运行您的应用程序时,纵向将显示标签栏,而横向将不显示标签栏。 让我们创建一个带有工具栏和屏幕上图像的应用程序。 为了检查尺寸分类的工作方式,我们将删除工具栏并更改iPhone横向方向的图像高度,即“紧凑高度-紧凑宽度”。 让我们将屏幕设计为任意高度-看起来像任何宽度: 现在,我们将删除高度紧凑的工具栏-紧凑的宽度。 为此,我们将选择紧凑的高度-尺寸类别中的紧凑的宽度。 然后,我们将选择工具栏并使用“命令+删除”从紧凑高度(紧凑宽度)中删除工具栏。 删除高度紧凑的工具栏(宽度紧凑)后,它在文档轮廓中将显得褪色。 让我们更改图像的高度以获得紧凑的高度-紧凑的宽度。 为此,我们将选择屏幕上的图像并打开尺寸检查器。 然后单击图像的高度约束。 如下所示,您可以在常量附近看到“ +”号。 现在,我们将单击该加号,它将打开一个小菜单,我们需要在其中选择紧凑的高度-紧凑的宽度。 当我们处于紧凑高度(紧凑宽度)时,我们会将图像高度的常数设置为170。 如下所示: 我们完了。 让我们检查一下Xcode预览中的屏幕外观 正如我们所看到的,我们所做的所有更改都反映了紧凑的高度-紧凑的宽度iPhone 3.5英寸横向。 当我们更改图像的高度时,我们可以更改特定标签的字体大小,并且它将针对所选的大小类别反映出来。 希望这篇博客文章在使用自动布局设计应用程序时提供有关使用大小类的信息。 可以从这里https://github.com/prajaktak/iOSSizeClassDemo随意收集示例,并进行试用。
在视图控制器中更改某些代码已经发生了多少次,并且在生产环境中测试应用程序时,视图已经发生了神奇的变化? 如果我们不想,我们是否应该有某种方法来确保视图不变? 因此,进行UI测试很重要。 通过添加UI测试,我们可以确保不会对UI进行意外更改。 在本文中,我们将在一个简单的示例项目中专注于FBSnapshotTestCase的用法。 FBSnapshotTestCase的历史 最初,FBSnapshotTestCase由Facebook创建和维护。 这是一个非常有用的工具,具有非常简单的实现。 所需要做的就是将库集成到项目中,并实例化我们要测试的UIView或UIViewController子类。 不幸的是,自去年以来,Facebook已停止维护该工具。 Facebook已经创建了另一个与其内部基础设施紧密相关的工具,因此他们不赞成该工具。 对我们来说幸运的是, Uber决定为他们当前维护的项目创建一个分支。 FBSnapshotTestCase测试实际上是单元测试,它使我们能够将UIViewController的参考图像(也可以是UIView,但通常是在全屏模式下)与视图的当前呈现进行比较。 如果存在任何不一致(第一张图像与第二张图像之间存在差异),FBSnapshotTestCase将生成第三张图像,并以灰度标出差异。 如果参考图像和项目中的电流之间存在差异,则该工具将生成此第三张图像,并且在该单元测试中会生成错误,从而导致测试套件产生故障。 Fastlane为我们提供了类似的工具Fastlane Snapshot(存储库),它使我们能够在Xcode UI测试中截取屏幕截图。 所有UI测试完成后,如果它们都获得批准,它将生成带有所有屏幕截图的HTML文件。 Fastlane Snapshot默认情况下还允许我们设置我们要查看的所有设备和语言。 这对于测试所有可能的组合非常有用(如果您有3种语言,应用程序中有10个屏幕,并且支持5种设备,则它会生成3 x 10 x 5 = 150个屏幕截图!)。 问题在于,这只是一个屏幕截图,它没有为我们带来有关图像是否具有正确UI的信息。 总而言之,FBSnapshotTestCase允许我们比较并告诉我们是否有任何错误,Fastlane Snapshot只是为我们带来了Xcode UI测试的屏幕截图。 我们将讨论以下主题,力图充分利用该工具的潜力: 如何集成FBSnapshotTestCase 使用SnapKit重构代码并使用FBSnapshotTestCase 编写脚本以生成具有不同分辨率的屏幕 要开始本教程,请下载以下示例项目https://github.com/fedejordan/FBSnapshotTestCaseExample 在模拟器中编译项目(我使用过iPhone 8),您将看到以下屏幕: 一个简单的View Controller,可以是任何应用程序的第一个屏幕,带有登录和注册按钮。 我已经使用CocoaPods集成了SDK。 您可以使用Carthage或您选择的包裹管理员。 集成该工具的步骤在存储库指南中。 为了方便起见,以下是使用CocoaPods的安装说明: 在终端应用程序中,我们输入pod init以创建podfile 在podfile中,我们添加pod ‘FBSnapshotTestCase’ 我们在终端中进行pod install以下载SDK 我在这里使用的版本是2.1.6 将SDK集成到项目中后,我们将打开CocoaPods生成的工作区并编译项目,以确保一切正常。 就像它在FBSnapshotTestCase官方指南中所说的那样,我们转到Edit […]