任何工程师都在努力使自己的工作过程尽可能优化。 作为移动iOS开发人员,我们经常不得不使用统一的语言结构。 苹果正在通过努力使我们易于编程来改进开发人员工具:语言突出显示,自动完成方法以及许多其他IDE功能使我们的头脑与思想保持同步。

缺少所需工具时,工程师会做什么? 没错,他会自己做的一切! 之前我们
讨论过创建自定义工具的问题,现在我们来讨论如何修改Xcode并使它根据您的规则工作。
我们从
JIRA Swift获得了任务,并制作了一个工具,该工具可以将
let转换为等效的
保护let结构。

从第九版开始,Xcode提供了一种新的重构机制,当您重命名多个文件中出现的方法或属性时,即使它们使用不同的语言,也可以在同一Swift源文件中本地或全局地转换代码。
本地重构在SourceKit编译器和框架中完全实现,该功能位于开源Swift储存库中,并使用C ++编写。 普通人目前无法修改全局重构,因为Xcode代码库是封闭的。 因此,我们将详细介绍当地历史,并讨论如何重复我们的经验。
您需要创建自己的本地重构工具:
- 了解C ++
- 编译器基础知识
- 了解什么是AST以及如何使用它
- Swift源代码
- 指南swift /文档/重构/ SwiftLocalRefactoring.md
- 非常耐心
关于AST的一些知识
在深入实践之前需要一些理论基础。 让我们看一下Swift编译器架构的工作方式。 首先,编译器负责将代码转换为可执行的机器代码。

在所示的转换阶段中,对我们而言最有趣的是生成抽象语法树(AST)-一种图形,其中顶点是运算符,而叶子是它们的操作数。

语法树用于解析器中。 AST用作计算机程序的编译器/解释器的内部表示形式,用于优化和生成代码。
生成AST之后,将执行分析以创建带有类型检查的AST,该类型检查已转换为Swift中间语言。 SIL被转换,优化,降级为LLVM IR,最终被编译为机器代码。
要创建重构工具,我们需要了解AST并能够使用它。 因此,该工具将能够正确处理我们要处理的部分代码。
要生成文件的AST,请运行以下命令:swiftc -dump-ast
MyFile.swift
以下是前面提到的if let函数到 AST 控制台的输出 。

Swift AST中有三种主要类型的节点:
- 声明(Decl类型的子类),
- 表达式(类型为Expr的子类),
- 运算符(类型为Stmt的子类)。
它们对应于Swift语言本身使用的三个实体。 函数,结构,参数的名称是声明。 表达式是返回值的实体。 例如,调用函数。 运算符是语言的一部分,用于定义代码执行的控制流,但不返回值(例如,if或do-catch)。
这是您接下来的工作中需要了解的足够的AST知识。
重构工具如何在理论上起作用
要实现重构工具,您需要有关要更改的代码区域的特定信息。 为开发人员提供了累积数据的辅助实体。 第一个是ResolvedCursorInfo(基于光标的重构),它告诉您我们是否位于表达式的开头。 如果是这样,则返回此表达式的相应编译器对象。 第二个实体RangeInfo(基于范围的重构)封装有关原始范围的数据(例如,它具有多少个入口和出口点)。
基于光标的重构由源文件中光标的位置启动。 重构动作实现了重构机制用来在IDE中显示可用动作并执行转换的方法。 基于光标的操作示例:跳转至定义,快速帮助等。

考虑技术方面的常规操作:
- 当您从Xcode编辑器中选择一个位置时,将向sourcekitd (负责突出显示,代码完成等的框架)发出请求,以显示可用的重构动作。
- ResolvedCursorInfo对象请求每个可用操作,以检查此操作是否适用于所选代码。
- 适用操作的列表作为响应从sourcekitd返回,并显示在Xcode中。
- 然后,Xcode将更改应用到重构工具。
通过在源文件中选择连续范围的代码来启动基于范围的重构。

在这种情况下,重构工具将经历所描述的类似调用链。 区别在于,在实现时,输入的是RangeInfo而不是ResolvedCursorInfo。 有兴趣的读者可以参考
Refactoring.cpp以获得有关Apple工具箱示例的更多信息。
现在开始创建工具的实践。
准备工作
首先,您需要下载并构建Swift编译器。 详细说明在官方存储库(
readme.md )中。 以下是用于代码克隆的关键命令:
mkdir swift-source cd swift-source git clone https:
Cmake用于描述项目的结构和依赖性。 使用它,由于以下命令之一,您可以为Xcode(更方便)或ninja(更快)生成项目:
./utils/build-script --debug --xcode
或
swift/utils/build-script --debug-debuginfo
成功的编译需要最新版本的Xcode beta(在撰写本文时为10.2.1)-可在
Apple官方网站上获得 。 要使用新的Xcode构建项目,您需要使用xcode-select实用工具注册路径:
sudo xcode-select -s /Users/username/Xcode.app
如果我们分别使用--xcode标志为Xcode构建项目,那么在build文件夹中经过几个小时的编译(两个多一点)之后,我们将找到Swift.xcodeproj文件。 打开项目,我们将看到熟悉的带有索引,断点的Xcode。
要创建新的乐器,我们需要将具有乐器逻辑的代码添加到文件:lib / IDE / Refactoring.cpp并定义两个方法isApplicable和performChange。 在第一种方法中,我们决定是否为所选代码输出重构选项。 第二,如何转换所选代码以应用重构。
准备完成后,仍然要执行以下步骤:
- 开发工具逻辑(可以通过多种方式完成开发-通过工具链,通过Ninja,通过Xcode;所有选项将在下面描述)
- 实现两种方法:isApplicable和performChange(它们负责访问工具及其操作)
- 在将PR发送到官方Swift存储库之前,请诊断并测试完成的工具。
通过工具链测试工具操作
由于组件的组装时间很长,因此这种开发方法将花费大量时间,但是结果在Xcode(可手动验证的方法)中立即可见。
首先,让我们使用以下命令构建Swift工具链:
./utils/build-toolchain some_bundle_id
编译工具链将比编译编译器及其依赖项花费更长的时间。 输出是swift-nightly-install文件夹中的文件swift-LOCAL-yyyy-mm-dd.xctoolchain,您需要将其传输到Xcode:/ Library / Developer / Toolchains /。 接下来,在IDE设置中,选择新的工具链,重新启动Xcode。

选择该工具应处理的一段代码,然后在上下文菜单中查找该工具。
通过忍者测试进行开发
如果该项目是为Ninja开发的,并且您选择了TDD路径,那么通过Ninja测试进行开发是最适合您的选择之一。 缺点-您无法设置断点,就像通过Xcode开发一样。
因此,当用户在源代码中选择防护结构时,我们需要检查新工具是否在Xcode中显示。 我们在现有的test / refactoring / RefactoringKind / basic.swift文件中编写测试:
func testConvertToGuardExpr(idxOpt: Int?) { if let idx = idxOpt { print(idx) } }
我们指出,当突出显示266行3列和268行4列之间的代码时,我们期望菜单项具有新工具的外观。
使用
lit.py脚本可以为您的开发周期提供更快的反馈。 您可以指定感兴趣的测试服。 在我们的情况下,此套件将是RefactoringKind:
./llvm/utils/lit/lit.py -sv ./build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/test-macosx-x86_64/refactoring/RefactoringKind/
结果,将仅测试该文件。 它们的实施将花费数十秒。 稍后将在“诊断和测试”部分中讨论有关lit.py的更多信息。
测试失败,这对于TDD范例来说是正常的。 毕竟,到目前为止,我们还没有使用该工具的逻辑编写任何代码。
通过调试和Xcode开发
最后,这是在Xcode下构建项目时的最后一种开发方法。 主要优点是能够设置断点和控制调试。
在Xcode下构建项目时,将在build / Xcode-DebugAssert / swift-macosx-x86_64 /文件夹中创建Swift.xcodeproj文件。 首次打开此文件时,最好选择手动创建方案以生成ALL_BUILD并自行快速重构:

接下来,我们使用ALL_BUILD一次构建项目,然后使用快速重构方案。
重构工具被编译成一个单独的可执行文件-swift-refactor。 可以使用–help标志显示该文件的帮助。 对我们来说最有趣的参数是:
-source-filename=<string>
可以在模式中将它们指定为参数。 现在,您可以设置断点以在启动工具时在感兴趣的地方停止。 通常,使用Xcode控制台中的
p和
po命令,它显示相应变量的值。

IsApplicable实现
isApplicable方法接受ResolvedRangeInfo,该ResolvedRangeInfo在输入中包含有关所选代码片段的AST节点的信息。 在该方法的输出处,确定是否在Xcode上下文菜单中显示该工具。 完整的ResolvedRangeInfo接口可以在
include / swift / IDE / Utils.h文件中找到 。
考虑一下在我们的案例中最有用的ResolvedRangeInfo类的字段:
- RangeKind-首先要做的是检查所选区域的类型。 如果区域无效(Invalid),则可以返回false。 如果类型适合我们,例如SingleStatement或MultiStatement,则继续进行;
- ContainedNodes-属于所选范围的AST元素数组。 我们要确保用户选择if let构造输入的范围。 为此,我们采用数组的第一个元素,并检查该元素是否与IfStmt(定义if子类型的语句节点的AST节点的类)相对应。 接下来,查看条件。 为了简化实现,我们将仅针对具有一个条件的表达式输出该工具。 根据条件的类型(CK_PatternBinding),我们确定这是允许的。

要测试isApplicable,请将示例代码添加到文件
test / refactoring / RefactoringKind / basic.swift中 。

为了使测试模拟对我们工具的调用,您需要在
tools / swift-refactor / swift-refactor.cpp文件中添加一行。

我们实现performChange
在上下文菜单中选择重构工具时,将调用此方法。 该方法可以访问ResolvedRangeInfo,也可以访问isApplicable。 我们使用ResolvedRangeInfo并编写代码转换工具的逻辑。
为静态令牌生成代码时(由语言语法调节),可以使用tok名称空间中的实体。 例如,对于guard关键字,请使用tok :: kw_guard。 对于动态令牌(由开发人员修改,例如,函数的名称),您需要从AST元素数组中选择它们。
为了确定转换后的代码插入的位置,我们使用RangeInfo.ContentRange构造使用整个选定范围。

诊断与测试
在完成使用工具之前,您需要再次检查其工作的正确性。 测试将再次帮助我们。 测试可以一次运行,也可以在所有可用范围内运行。 运行整个Swift测试套件的最简单方法是在utils / build-script上使用--test命令,它将运行主测试套件。 使用utils / build-script将重建所有目标,这会大大增加调试周期。
在对编译器或API进行重大更改之前,请确保运行utils / build-script --validation-test验证测试。
还有另一种运行编译器的所有单元测试的方法-通过ninja,来自build / preset / swift-macosx-x86_64的ninja check-swift。 大约需要15分钟。
最后是需要单独运行测试的选项。 要直接从LLVM调用lit.py脚本,必须将其配置为使用本地构建目录。 例如:
% $ {LLVM_SOURCE_ROOT} /utils/lit/lit.py -sv $ {SWIFT_BUILD_DIR} / test-macosx-x86_64 / Parse /
这将在64位macOS的“ test / Parse /”目录中运行测试。 -sv选项提供了测试执行的指示,并仅显示失败测试的结果。
Lit.py还有一些其他有用的功能,例如定时测试和延迟测试。 您可以使用lit.py -h查看这些功能和其他功能。 最有用的可以在
这里找到。
要运行一个测试,请编写:
./llvm/utils/lit/lit.py -sv ./build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/test-macosx-x86_64/refactoring/RefactoringKind/basic.swift
如果我们需要获取最新的编译器更改,那么我们需要更新所有依赖项并重新设置基础。 要升级,请运行./utils/update-checkout。
结论
我们设法实现了我们的目标-开发一个以前在IDE中不存在的工具来优化工作。 如果您还对如何改善Apple产品以及使整个iOS社区的生活变得更加轻松有想法,请随时进行反品牌宣传,因为它比乍看起来似乎容易!
2015年,Apple将Swift源代码上载到公共领域,这使得深入研究其编译器的实现细节成为可能。 此外,使用Xcode 9,您可以添加本地重构工具。 具有C ++和编译器设备的基本知识足以使您喜欢的IDE更加方便。
所描述的经验对我们很有用-除了创建简化开发过程的工具之外,我们还获得了真正的语言核心知识。 稍微打开的带有低级流程的Pandora盒子可让您从新的角度看待日常任务。
我们希望所获得的知识也能丰富您对发展的理解!
该材料与
@victoriaqb -iOS开发人员Victoria Kashlina共同编写。
资料来源
- Swift编译器设备。 第二部分
- 如何构建基于Swift编译器的工具? 分步指南
- 为iOS项目转储Swift AST
- 介绍sourcekitd压力测试仪
- 测试迅速
- [SR-5744]重构操作,将if-let转换为guard-let,反之亦然#24566