Tag: xcode

Des Testing unitaires en Swift avec Xcode

最终测试标准考试和天体测试考试。 Nous allons维护者nous attarder sur la classegénéréeafin de vous expliquer la base。 导入XCTest 类AstronauteTests:XCTestCase { 覆盖func setUp(){ super.setUp() } 覆盖func tearDown(){ super.tearDown() } func testExample(){ } func testPerformanceExample(){ 自我衡量{ } } } 总理选择了重要的XCTest。 Comme vous vous en doutez,高级框架XCTest。 随员的套房: setUp() 。 Cétéméthodeestappeléeavant chaque invocation de chaqueméthodede testécritdans la classe。 将字体倒入测试字体,并替换字体; tearDown() 。 测试的最佳方法之一是等级考试; testExample() 。 […]

iOS XCUITest –黄瓜

在XCode中让iOS工程师对BDD感到兴奋。 免责声明:本主题与上下文有关,如何使用Cucumberish可以使iOS开发工程师或UI自动化测试团队受益。 本文旨在通过使用Cucumberish作为其本机iOS开发的一部分来使iOS工程师兴奋。 在介绍Cucumberish库之前,我想快速介绍一些初始要点。 什么是黄瓜/黄瓜/行为驱动开发(BDD) 黄瓜测试的结构 跨平台移动测试工具的一些注意事项 我是一名iOS工程师,并且一直在编写各种iOS自动化测试框架:UIAutomation(使用javascript),Calabash(使用ruby),Sen Testing(目标C),“ XCTest + Gherkin” cocoapods,以及目前的“ Cucumberish”。 我还找到了一种编写XCUITest来断言Google Analyics网络有效负载的方法。 这是一个非常有趣的主题,但是我将其保存在另一篇文章/演讲中……敬请期待。 1.什么是黄瓜/黄瓜/行为驱动的发展(BDD) Cucumber是一种软件工具,用于使用Gherkin语言执行行为驱动的测试(BDD)。 Gherkin Language本身与平台无关,它为产品所有者和业务分析人员提供了结构化的模板,以通过用例描述业务需求。 它的主要特征是“ Given-When-Then”子句的存在。 行为驱动开发(BDD) 与产品所有者和利益相关者合作以拟定可为客户带来价值的用例规范列表的过程。 这些规范使用Ghenn-When-Then Gherkin语言写入“功能”文件。 即使非工程师也很容易理解功能文件,这促进了整个组织的协作。 可以使用BDD工具(例如黄瓜,阿皮,黄瓜,葫芦,硒)将其自动化。 对于跨平台测试,功能文件是组织具有可在所有平台上使用的集中式规范的绝佳选择。 上面的BDD示例可以在Web和移动应用程序上使用。 在敏捷开发过程中常见。 2.黄瓜测试的结构 黄瓜测试套件包含一组功能文件。 它们每个都有一个“ .feature”扩展名。 每 功能文件包含一个或多个描述软件特定功能的方案。 使用多行用Gherkin编写的“步骤”来编写方案。 步骤定义是一小段代码 ,上面附加了一个模式 。 该模式用于将步骤定义链接到所有匹配的步骤,并且代码是Cucumber看到小黄瓜步骤时将执行的代码 。 [https://cucumber.io/docs/reference#step-definitions] 一旦定义了步骤,就可以继续为每个步骤编写测试/断言实现 。 请注意,如果使用适当的正则表达式格式,则可以使您的步骤对于测试套件更可重用。 3.关于跨平台移动测试工具的一些说明 在移动领域,市场上有一些工具试图通过引入单独的测试运行器来简化跨平台测试的复杂性。 iOS中使用的最受欢迎的是Calabash和Appium。 在我看来,以上问题是工程团队需要投资的额外开销。实现跨平台功能的另一种想法是,仅跨平台绑定通用功能文件,并允许灵活地选择最合适的测试运行器进行开发效率。 对于iOS开发,理想的情况是我们可以在XCode中执行Gherkin BDD功能文件,并且其步骤定义/实现可以用Swift编写。 哦,是的,还有一个更理想的标准:通过Cocoapods安装! […]

管理XCTest的iOS构建配置和方案

最初在 这里 发布在XCBlog上 在将iOS应用发布到Apple App Store之前,通常有一个针对内部受众的构建,即质量检查工程师,仅在特定配置的设备上运行的产品所有者。 在此过程中,您可能会听到诸如DEBUG或RELEASE配置之类的信息。 当我们使用Xcode创建新的iOS项目时,Apple为我们提供了两个项目级别的构建配置,即调试和发布。 调试配置通常用于开发和内部项目设置,例如将应用程序指向测试环境,然后使用发行版将应用程序提交到App Store。 但是,在许多情况下,可能会触发对更多内部设置或其他构建配置的需求。 作为一名优秀的iOS开发人员,您必须使用XCTest框架编写单元和UI测试。 通常,开发人员使用调试构建配置或运行单元和UI测试,但是为XCTest创建单独的构建配置会使测试更加独立,可靠和确定性。 在本文中,我们将看到如何为XCTest创建单独的构建配置。 构建配置 在进入创建新的构建配置之前,让我们看看什么是iOS中的构建配置以及为什么使用正确的配置构建应用很重要。 想象一下,我们已经在Xcode中创建了全新的iOS应用。 它将有两个构建配置,分别在iOS项目的“构建设置”中进行调试和发布。 在编译和构建iOS应用程序时,幕后发生了很多事情,例如编译,链接,复制包资源,运行构建脚本等。我们始终可以在xcodebuild日志中看到这些事情。 构建配置定义了我们希望如何在特定设置下构建应用程序,例如,当我们使用模拟器开发应用程序时,“调试信息格式”构建设置不需要生成dSYM文件,因此我们可以使用DWARF进行调试构建。 但是,我们需要发布版本的信息,以便我们可以将DWARF与dSYM文件设置一起使用。 如上图所示,您可以在Xcode的所有构建设置中看到这一点。 简而言之,构建配置定义了一种机制,该机制应如何使用不同的条件或设置构建应用。 在运行使用XCTest框架编写的单元或UI测试时,仍然可以使用调试配置,但是,添加其他配置可以使我们对测试过程进行大量控制。 XCTest的构建配置 现在,我们将添加另一个仅可用于XCTest单元或UITests的构建配置。 通常,单元测试需要较少的配置,因为我们可以直接在应用程序内部访问数据和API。 但是,UITests或XCUITests完全是黑盒子,因此我们需要在那里进行更多配置,以便通过UI测试的特定设置。 让我们从Xcode为示例iOS应用添加另一个构建配置开始。 在Xcode项目级别设置中,选择“信息”选项卡,我们将看到可用配置的列表以及添加更多配置的选项 注意,我们必须进行项目级别的配置才能获得此选项,而不是目标级别的配置。 单击配置中的+按钮。 我们可以通过复制调试或发布配置来添加其他配置。 通常,为了测试目的,我们需要重复调​​试配置,因此我们将其称为xctest在“ 配置”部分下,按+按钮。 选择“ 重复调试配置” 。 命名您的新配置xctest 现在,我们有了全新的构建配置,可用于单元测试或UI测试。 我们可以将用户定义的设置分配给新配置,以便我们可以设置测试特定的配置。 在项目构建设置中,向下滚动到构建设置的最底部,有一个名为User-Defined的部分。 这是将添加和设置配置变量的位置。 预处理器宏 现在,我们添加了新的构建配置,是时候更改预处理器宏了,以便我们可以设置源代码有条件地执行一些代码 现在,我们可以使用调用测试代码的主应用程序中的代码来唤醒此配置。 #if XCTEST //仅测试代码版本代码 #其他 //仅应用程式程式码 #万一 同样,我们可以使用相同的方法来更改不同的环境,例如阶段,测试,但是某些文章已经介绍了该方法。 一些受欢迎的文章是 Xcode项目中的多个构建配置 […]

// TODO:—使您在Xcode上的注释突出

留下笔记的做法很重要。 对项目进行注释可以帮助您和您的同事跟踪已完成的工作以及尚需完成的工作。 Xcode中内置了三个关键字(TODO,FIXME和MARK),以帮助跟踪项目中需要完成的许多工作。 此外,在脚本编写的帮助下,您可以扩展这些关键字的功能,以确保您的笔记不会被忽略。 在家玩,看看它是如何工作的! 关键词 在Xcode中,打开一个.swift文件。 在代码编辑器上方,您会看到一个状态栏,其中显示了swift文件的位置以及当前在文件中的位置。 单击最后一项将显示一个下拉菜单,列出属性,方法或文件中发生的任何其他情况。 这有助于使用许多代码行和许多功能读取和浏览文件。 Xcode还允许您将自己的项目放置在此列表中,以便其他人,甚至是漫长的一周后甚至您自己,也可以对文件进行快速诊断,以查看是否需要完成或需要修复。 例如: // TODO:添加方法来查找大海捞针 …将产生此结果,并带有项目符号列表图标: 这个: // FIXME:删除对jiltedExLover的所有引用 …将产生此消息,并带有绷带图标: 另一个技巧是,在任何音符上的冒号后面加一个破折号(-),将在下拉列表中创建一个换行符。 所以这: // MARK:—不同的部分 …将产生此消息,并在其上换行: 得到它了? 大! 带注释生成警告和错误 现在也许您在想,这有什么意义? 我是否还不必仔细研究程序,逐个文件,单击下拉菜单,尝试找到该TODO标签? 如果项目有数十个文件怎么办? 我的TODO和FIXME标签不会陷入混乱并被遗忘吗? 如果只有TODO标签没有答案,那么只有一种方法可以使Xcode发出警告! 你猜怎么了? 有! 通过使用一些bash,Xcode会针对任何给定的注释标签自动向您抛出警告或错误。 转到Xcode项目文件上的构建阶段。 单击trop右边的小加号,然后选择Run New Script Phase 。 这里的想法是,当Xcode构建项目时,您将在构建过程中增加另一步。 如果解开运行脚本项的箭头,则会看到一个黑框,其中包含Xcode将在每个版本上运行的脚本。 现在,将其复制并粘贴到该框中:

设计和编写自己的UIAlertView

AlertViews是在iOS中向用户显示消息的常用方法。 该视图使用户可以了解关键信息(电池电量低)或做出有关操作过程的决定(确定要删除此项目吗?) 对于这种用例,默认的UIAlertController(记住,不推荐使用UIAlertView!)是完美的,但是如果我需要一个更复杂的UI(包括滑块,Switched Segmented Control或从头开始设计的UI)怎么办? 我发现实现此目标的唯一方法是创建自定义UIAlertView。 如果您处在这种情况下,您可能会想:让我们对UIAlertController类进行子类化,并添加新功能和自定义内容! 然后,检查API参考并找到以下内容: 子类化 UIAlert​Controller类旨在按UIAlert​Controller使用,不支持子类化。 此类的视图层次结构是私有的,不能修改。 那我们现在怎么办? 🤔答案是:子类化UIViewController并将其行为更改为类似于UIAlertView的外观! 让我们看看如何。 我创建了一个简单的应用程序,以显示标准UIAlertView和自定义应用程序之间的区别。 用户界面 首先,设计AlertView的UI。 就我而言,我使用的是Storyboard,它看起来像这样。 我还添加了约束以使其响应。 控制器 然后,我们创建一个将继承UIViewController的类。 此类将处理所有AlertView的逻辑和用户操作:点按一个按钮,在UITextField上编写,为视图设置动画,将其关闭等等。 实现很简单,但是让我详细解释一些部分: alertTextField.becomeFirstResponder() 此方法默认请求文本字段焦点并打开键盘。 alertTextField.resignFirstResponder() 另一方面,此按钮关闭键盘并删除文本字段焦点。 view.layoutIfNeeded() 使用此方法可以在绘制之前强制子视图的布局。 如果您需要绘制按钮边框(在这种情况下),这很有用,因为它可以防止UI毛刺(如绘制两个边框):更改约束前后。 提示 :在按钮上画边框我发现Github上Isuru Nanayakkara的一个精要思想是使用Swift扩展,不要忘了🌟! 链接 self.view.backgroundColor = UIColor.black.withAlphaComponent(0.4) 将背景颜色设置为透明的黑色,以便使打开CustomAlertView的ViewController在背景上可见。 代表 如您所见,委托模式用于将我们的CustomAlertView与显示它的类进行通信。 实现方式是使用协议。 实例化 最后一件事是实例化并显示我们的CustomAlertView。 用委托协议扩展ViewController,以对CustomAlertView操作(在此示例中为取消和确定按钮)做出反应,实例化CustomAlertView,设置委托并呈现,完成! 其余代码用于正确设置CustomAlertView的动画,并像UIAlertController一样工作。 那是所有人! 它是一个非常简单的自定义AlertView,但是考虑到很少的事情,您可以根据需要/需要对其进行自定义。 与往常一样,欢迎您提供反馈,感谢您的阅读和❤️(如果您觉得有用)! 码 https://github.com/DrankoLQ/CustomAlertView 参考文献 [1] https://developer.apple.com/reference/uikit/uialertview [2] https://developer.apple.com/library/content/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html […]

Swift中的面向对象编程

苹果公司的大多数框架都具有面向对象的体系结构。 在开始研究iOS / MacOS开发之前,您应该首先了解面向对象的编程和设计模式。 在本文中,我们将介绍基本概念和设计模式,以帮助您开始进行应用程序开发。 总览 面向对象编程(OOP)是一种编程范式,代表了具有数据字段(描述对象的属性)和相关方法(称为方法)的“对象”的概念。 对象通常是类的实例,用于彼此交互以设计应用程序和计算机程序。 面向对象编程包含3个关键方面: 封装意味着对象将其状态信息保密。 其他对象不是直接处理对象的数据,而是以消息的形式向对象发送请求,对象可能通过更改其内部状态来响应其中的一些请求。 多态性意味着不同类别的对象可以互换使用。 这一点特别重要,因为它允许您以后以不必要的方式挂钩类,而不必事先预料这些类是何时设计的。 继承意味着一类的对象可以从另一类(基类或父类)派生其行为的一部分。 某些面向对象的语言(例如,C ++,但不支持Swift)允许多重继承,其中一类对象可以从多个独立的基类派生其部分或全部行为。 类和对象 在面向对象的编程中,类是用于创建对象,提供状态(成员变量)和行为实现(成员函数,方法)的初始值的可扩展程序代码模板。 换句话说,类就像一个蓝图,它定义了类型的数据和行为。 类按钮{ } 上面的定义创建一个名为Button的空类。 它唯一能做的就是创建新的Button对象: var button = Button() 在上面的示例中, button是Button类的实例。 物产 类和实例可以具有名为属性的关联值。 广场广场{ var length :Int = 1 } Square类的length属性的默认值为1 。 为了创建length不同于1正方形,我们需要编写一个自定义初始化程序。 广场广场{ var length :Int = 1 初始化 ( 长度 :整数){ self.length =长度 } } […]

协调员基本教程。 第二部分

在本教程的第一部分中,我总体上讨论了协调器的方法,并展示了一些与实现有关的常见示例。 为了与本文保持一致,请阅读上一部分。 在这一部分中,我想介绍一些使用协调器的极端情况。 我想最有趣的部分是: AppDelegate配置 管理启动选项 深度链接和协调员推送通知处理 我将逐步说明解决方案并进行解释。 如何使用主应用程序协调器配置AppDelegate? 由于所有导航逻辑都移至AppCoordinator,因此AppDelegate变得非常简单。 因此,您需要做的是创建一个协调器,注入rootController并调用start()。 我们希望通过展示一个教程,展示一个身份验证流程,向他们介绍新功能或仅显示主屏幕来向用户介绍我们的应用程序。 我们应该谨慎处理这种行为。 假设我们有3种不同的情况: 用户应查看入门教程(我们将显示入门屏幕) 用户应登录(我们显示登录屏幕) 用户已完成之前的两个步骤,仅看到主屏幕 为了轻松处理所有这些情况,我们创建了一个枚举作为协调者的助手。 该枚举应检查所有标志,并返回协调器应遵循的正确方案。 我将其命名为实体讲师,因为它指示协调员接下来要做什么。 怎么处理呢? 正如您在上一部分中所记得的,我告诉您有关BaseCoordinator的信息 ,其中包含子协调器数组。 所有协调员都继承它。 这意味着我们可以循环所有孩子并执行动作。 为此,我们只需添加一种方法即可处理DeepLink逻辑本身,也可以循环处理子代以找到可以处理它的方法。 它将像一棵树。 我们可以创建特定的协议来实现此目标。 让我们更新协调器协议: 现在,我们将两个带有空主体的方法添加到BaseCoordinator中,以使其保持可选状态。 孩子们可以继承这些方法之一,也可以两种都继承。 在某些情况下,可以使用不带选项的start来运行默认行为,但是如果继续进行深层链接方案,则应添加有关演示案例的信息。 如何实现呢? 更好的方法是在项目中添加一些实体,其中包含所有深层链接快捷方式,并创建可以使用start()方法发送的对象。 你想念枚举吗? 让我们添加一个新的! 它将是DeepLinkOption: 好处是,协调器中所有的DeepLink方法都将像切换为枚举一样。 是的,与上一个示例中的Instructor相同,您是正确的。 我们可以根据需要添加一些其他的build方法(带有…): 在此迭代之后,如果我们返回AppDelegate并对其进行一些更新,它将看起来像这样: 好了,看起来不错吗?🤔然后您将收到一个推送通知或深层链接,您只需构建您的DeepLink并通过AppCoordinator进行此操作即可。 结论 在我们尝试在代码中实现的每个新解决方案中,我们都面临着很多极端的情况,并且还面临着一些不太漂亮的解决方案。 它不应该像炒作那样驱动开发,我们只需要逐步改进所有棘手的部分即可。 如果您有任何疑问或新的特定问题,请随时给我写信。 并检查整个实现的Github回购。

使用Sourcery在iOS中自动生成委托存根

代表们,代表们! iOS喜欢代表。 它们在Apple的大多数框架中都使用过,我对您一无所知,但最终我在自己开发的每个应用程序中一遍又一遍地编写自己的应用程序。 在测试代​​码时,您必须编写许多手动存根和样板来测试调用委托的类,谁愿意编写比他们需要的代码更多的代码! 任何额外的工作都会使测试在整个团队中进行的可能性降低,并且在进行测试时会变得越来越精明。 每当您定义代理人BAM时,Sourcery都可以在此提供帮助。 在下一次编译时,会生成一个存根并将其添加到您的测试目标中,所有内容都会充实并准备就绪。 如果您不知道Sourcery是什么,它是一个元编程工具包,可以“ 扫描源代码,应用个人模板并为您生成Swift代码,从而使您可以使用元编程技术来节省时间并减少潜在的错误。” 这太疯狂了。 这篇文章不是Sourcery的完整介绍(尽管我们将在您的项目中进行设置)。 如果您想查看一个好的介绍,请转到Ray Wanderlich的教程。 它很好地解释了它并引导您完成它。 这是我们将做的一个例子: 代表定义: 协议AutoStub {} 协议OnboardingViewDelegate:AutoStub { func tappedLogin(用户名:字符串,密码:字符串)->布尔 func tappedRegister(用户名:字符串,密码:字符串) } 生成的存根: //使用Sourcery 0.7.2生成-https://github.com/krzysztofzablocki/Sourcery //不要编辑 类OnboardingViewDelegateStub:OnboardingViewDelegate { var namedTappedLogin:Bool = false var称为TappedLoginWithParamUsername:字符串? var namedTappedLoginWithParamPassword:字符串? var tappedLoginReturnValue:布尔? func tappedLogin(用户名:字符串,密码:字符串)->布尔{ self.drawnTappedLogin = true self.CalledTappedLoginWithParamUsername =用户名 self.CalledTappedLoginWithParamPassword =密码 返回self.tappedLoginReturnValue! } var namedTappedRegister:Bool = false […]

使用Xcode Instruments诊断和解决性能问题

我们将创建一个简单的iOS应用程序,该应用程序在表视图中使用名称和头像来显示模拟动物列表。 阿凡达图片存储在我们的应用程序捆绑包中。 为了使应用程序表现得更真实,我们将实时分别加载化身图像,而不是使用imageNamed:方法预加载化身图像。 我们可以假设瓶颈是图像加载。 我们正在从闪存驱动器中实时加载图像,而不是对其进行缓存。 所以这可能就是为什么它很慢吧? 可以使用一些很棒的代码来修复该问题,这些代码将使用GCD异步地推测性加载我们的图像,然后对其进行缓存。 在开始编写代码之前,让我们测试一下我们的假设。 让我们使用仪器工具来分析应用,以识别问题。 首先,我们对工具感兴趣,例如: Time Profiler:用于测量CPU使用率,按方法/功能细分。 核心动画:用于调试各种核心动画性能问题。 时间分析器 Time Profiler是用于监视CPU使用率的工具。 它使我们可以了解应用程序中哪些方法消耗最多的CPU时间。 高CPU使用率并不总是您应该意识到的-您最可能希望动画例程非常占用CPU,因为动画往往是iOS设备上最苛刻的任务之一。 如果您遇到性能问题,那么查看CPU时间是确定性能是否受CPU限制以及哪些方法需要优化的好方法。 核心动画 核心动画工具用于监视核心动画性能。 它给出了定期采样的FPS的细分,并考虑了发生在我们应用程序之外的部分动画。 核心动画工具还提供了许多调试选项,以帮助调试渲染瓶颈。 我们对选项感兴趣,例如: 颜色混合层:此选项突出显示正在发生混合的屏幕的任何区域,根据严重程度从绿色变为红色。 混合不利于GPU性能。 它会导致透支,并且是滚动或动画帧速不佳的常见原因。 颜色 shouldRasterize 绿色, shouldRasterize 红色:使用shouldRasterize属性时,将缓存昂贵的图层图形并将其渲染为单个展平的图像。 当必须重新生成缓存时,此选项以红色突出显示栅格化的图层。 如果频繁重新生成缓存,则表明光栅化可能会对性能产生负面影响。 屏幕外渲染的颜色为黄色:以黄色突出显示需要屏幕外渲染的任何层。 这些层可能是使用诸如shadowPath或shouldRasterize优化的候选shouldRasterize 。 我们的考虑可以归结为这种问题可能与图像加载有关的想法,所以让我们从Time Profiler工具开始。 在tableView: cellForRowAtIndexPath:方法(这是我们加载图像)中花费的CPU时间的总百分比仅为6%。 那真的不是那么高。 这可能是一个建议,说明CPU / IO不是此处的限制因素。 让我们看看这是否是GPU问题。 我们将检查GPU利用率。 GPU的平均硬件利用率约为72%。 看起来GPU不得不非常努力地渲染动物列表。 为什么GPU使用率如此之高? 让我们使用“核心动画”工具的调试选项检查屏幕。 首先,启用“ 颜色混合图层”选项。 屏幕上所有的红色表示文本标签上的混合水平很高,这并不奇怪,因为我们必须使背景透明才能应用阴影效果。 这就解释了为什么渲染器利用率如此之高。 […]

使用Xcode 9工具查找运行时错误

可能的情况 1 –数据竞赛 在多个线程之间共享的任何可变数据都需要访问同步。 如果在共享的可变变量上缺少同步 ,则意味着您存在数据竞争。 在存在数据争用的情况下,我们的程序可能会发生内存损坏和崩溃。 这些问题适用于C语言,也适用于Swift代码。 因此,让我们看一下Swift中的示例。 在这种情况下,我们有一个名为EventLog的类,它仅具有一个简单的名为log的函数,该函数将一些文本消息输出到输出中。 但是它也跟踪哪个是调用该log方法的最后一个事件源。 它将信息保存到一个名为lastEventSource的存储属性中,该属性是一个可选变量,在开始时为nil,但是一旦有人调用log,就会使用该特定日志源完美填充该信息。 现在让我们说我们有两个线程都试图同时调用该log方法。 假设第一个线程是我们的网络子系统,并且记录了一些下载已完成。 当第二个线程(代表我们的数据库子系统)正在记录查询已完成时: 那是一场数据竞赛,因为我们正在同时访问相同的内存位置。 Thread Sanitizer将对此发出警告! 要解决此问题,我们需要引入同步,最简单的方法是使用串行调度队列。 现在,由于此队列是串行的,因此一次只能执行一个工作项。 因此,如果将log函数的主体包装到queue.async中,将提供正确的同步。 并请注意,我在这里使用async是因为我们不需要等待该函数完成,因为该函数不会提供任何结果,因此等待它是没有意义的。 因此,这不仅解决了该问题,而且还提高了性能,因为现在无论呼叫日志的人都不再需要等待此打印完成。 这样,整个类现在都是线程安全的,我们可以从多个线程调用log 。 由Grand Central Dispatch或GCD提供的Dispatch队列在Swift中很容易获得,它们应该是同步的首选。 即使还有其他提供同步的机制,GCD也非常轻巧,并且可以从Swift轻松使用。 提示:一个好主意是将数据与串行调度队列相关联,并且仅访问这些队列中的数据,这将确保您仅以同步方式使用数据。