前言
Xcode是一个每天都有成千上万开发者使用的IDE( 集成开发环境),它是一个非棒棒的工具,但是有时候为了提高开发效率开发者可能想自定义一些它的功能和用途。在Xcode 7的时候,开发者可以在Xccode运行的时候通过注入代码去实现插件的功能。插件可以在Alcatraz这个优秀的APP上面提交和分发。不过这一切在在Xcode 8上已经不再可能。
Xcode 8验证每个库和包以防止恶意代码未经您的许可运行。当Xcode启动的时候,先前通过Alcatraz安装的插件不会再被加载。为了让开发者可以继续使用插件,苹果公司在今年的WWDC上宣布了可以通过开发Xcode source editor extensions使得每个人都可以扩展现有的源代码编辑功能。让我们一起来看看通过这些extensions我们能做什么。
1. 好的开端
Xcode 8 source editor extensions 是一个好的开始。如果开发者使用Xcode工作了一段时间,开发者可能会迫切地希望一些具体的工作可以在Xcode里面自动完成。Source editor extensions允许第三方应用程序去修改源文件,这可以帮助开发者提高工作效率。
现阶段,extensions只能和源代码交互。这就意味着不是每一个Alcatraz的插件都可以被source editor extension取代。但是谁知道未来会带来什么?(译者注:作者的意思苹果会不断完善这个功能的)。
每个extension都必须要要包含在一个macOS app里面。比如,开发者可以给extensions添加偏好设置和关于它可以用来做什么的解释说明,然后可以把它提交到Mac App Store。还要注意的是,每个extension运行在独立的进程里面,如果extension崩溃了,不会引起Xcode不会崩溃,不过它会提示一个信息表明extension不能完成它的工作了。
此外,extensions还没有UI交互,它们仅仅能够在开发者调用相关命令的时候直接的修改代码。而且它们不能在后台运行。
我建议观看 WWDC 2016 session about source editor extensions,它不仅仅解释了如何开发 Xcode source editor extensions,同时也展示了一些技巧和快捷方式去加速开发。
2. 概述
在这个教程中,我们将开发一个extension用于清楚Swift语言中的闭包语法。Xcode自动使用括号完成了一个闭包语法。不过为了简单起见,我们可以省略它们。这个任务是很容易自动完成的如果把它封装到source editor extension里面。
我们到底要开发什么呢?简单的解释就是实现一个可以把闭包转化为更简单更简洁语法的extension。下面来看一个具体的例子。
// Before
session.dataTask(with: url) { (data, response, error) in
}
// After
session.dataTask(with: url) { data, response, error in
}
3. 安装工程
首先,需要安装Xcode 8,开发者可以从Apple’s developer website下载。Xcode 8在OS X 10.11和macOS 10.12都可以运行。
创建一个新的类型为 Cocoa Application 的OS X项目,项目命名为 CleanClosureSyntax 。确保开发者选择的语言是 swift,因为在该教程中将会使用 Swift 3 。
创建好项目之后,我们把注意力集中到创建 Xcode source editor extension上面来。打开File菜单,选择New->Target,在左边的面板上,选择OS X然后从列表中选择 Xcode Source Editor Extension
点击Next然后设置Product Name为Cleaner,如果Xcode提示开发者新创建的Scheme是否需要激活,点击Activate激活Scheme。
4.项目结构
我们一起来分析一下Xcode给我们创建了什么。打开Cleaner去看它包含些什么。
该教程中,我们不会修改 SourceEditorExtension.swift ,但是如果以后开发者要更进一步的自定义extension的话,可能会使用到它。extensionDidFinishLaunching()
在extension启动的时候会被调用,如果需要的话开发者可以在此方法里面做一些初始化的工作。commandDefinitions
属性的getter方法可以动态的展示或是隐藏特定的指令。
SourceEditorCommand.swift是整个extension的核心。在此文件里面实现extension的相关逻辑。perform(with:completionHandler:)
方法在用户启动开发者的extension的时候被调用。XCSourceEditorCommandInvocation
对象包含了一个buffer
属性,这个属性主要是用来访问当前选中文件的源代码。如果一切顺利的话,completionhandler
将会以参数为nil
进行调用,否则将会给它传递一个NSError
实例作为参数。
5. 实现extension
现在工程里面已经包含了所有需要的targets,我们将要开始开发extension。概况的说,我们将要移除Swift文件中所有闭包里面的括号。主要分以下三步:
- 找到包含闭包的行
- 从特定的行里面移除两个括号
重置被修改的行(译者注:原文是substitute back the modified line ,结合代码感觉重置比较恰当)
我们可以使用正则表达式去遍历每一行代码是否含有闭包。如果开发者想进一步学习正则表达式可以参考Akiel的教程Swift and regular expressions 。开发者可以使用RegExr 去测试正则表达式。看下面的截图看看我是怎么测试正则表达式的。
打开SourceEditorCommand.swift 文件修改perform(with:completionHandler:)
方法为如下
func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: (NSError?) -> Void ) -> Void {
var updatedLineIndexes = [Int]()
// 1. Find lines that contain a closure syntax
for lineIndex in 0 ..< invocation.buffer.lines.count {
let line = invocation.buffer.lines[lineIndex] as! NSString
do {
let regex = try RegularExpression(pattern: "\\{.*\\(.+\\).+in", options: .caseInsensitive)
let range = NSRange(0 ..< line.length)
let results = regex.matches(in: line as String, options: .reportProgress, range: range)
// 2. When a closure is found, clean up its syntax
_ = results.map { result in
let cleanLine = line.remove(characters: ["(", ")"], in: result.range)
updatedLineIndexes.append(lineIndex)
invocation.buffer.lines[lineIndex] = cleanLine
}
} catch {
completionHandler(error as NSError)
}
}
// 3. If at least a line was changed, create an array of changes and pass it to the buffer selections
if !updatedLineIndexes.isEmpty {
let updatedSelections: [XCSourceTextRange] = updatedLineIndexes.map { lineIndex in
let lineSelection = XCSourceTextRange()
lineSelection.start = XCSourceTextPosition(line: lineIndex, column: 0)
lineSelection.end = XCSourceTextPosition(line: lineIndex, column: 0)
return lineSelection
}
invocation.buffer.selections.setArray(updatedSelections)
}
completionHandler(nil)
}
找到使用闭包的行
首先创建了一个Int
类型的数组用于存储修改过的行。这样就不需要更新所有的行,只需要替换掉被修改过的行。
枚举invocation.buffer
对象中所有的行,找出和RegularExpression
匹配的对象。移除掉正则表达式中的转义字符,正则表达式如下:
{.*(.+).+in
正则将会匹配当字符串满足如下特性的时候
- 有一个花括号(
{
),后面跟着若干个字符但是不包括换行符(\n
)。 - 需要有一个开括号(
(
),后面跟着若干字符,该部分包含的是闭包的参数。 - 然后需要有一个闭括号(
)
),后面跟着若干字符,这部分字符是可选返回类型 - 最后需要包含关键字
in
如果RegularExpression
对象匹配失败,则在调用completionHandler
时把error作为参数。如果某一行字符串匹配所有的条件,那就说明闭包已经找到。
移除闭包里面括号
当满足条件的闭包被找到后,调用NSString
里面的工具方法去移除括号。在调用的时候需要传入闭包的范围(译者注:就是NSRange)从而避免移除闭包之外的其他括号。
更新行
最后一步代码检查是否至少有一行被找到。如果条件成立,调用setArray()
在正确的位置重置新的代码。此时,传入nil
作为参数调用completionHandler
,告诉Xcode 开发者自定义的extension完成了正确的工作。
最后,实现NSString
的remove(characters:range:)
方法。添加NSString
的extension
(译者注:此处的extension不同于本文一直讲解的extension,此处是Swift的一种基本语法,是类的扩展)到文件里面。
extension NSString {
// Remove the given characters in the range
func remove(characters: [Character], in range: NSRange) -> NSString {
var cleanString = self
for char in characters {
cleanString = cleanString.replacingOccurrences(of: String(char), with: "", options: .caseInsensitiveSearch, range: range)
}
return cleanString
}
}
6. 测试
Xcode 8 带来了一个非常优秀方法用来测试extensions。首先,如果如果开发者的Xcode是运行在 OS X 10.11 El Capitan的话,打开Terminal,执行下面的命令,然后重启Mac。
sudo /usr/libexec/xpccachectl
做完以上工作后,选择正确的scheme后编译运行extensions。当询问开发者去运行哪一个App的时候,查找Xcode并且确保选择了Xcode 8 Beta版本(译者注:作者写此文章时候正式版还没有发布),一个新的灰色图标Xcode被打开,这样可以便于开发者区分哪一个Xcode是用来测试extension的。
在新的Xcode实例中,创建一个新的工程或是打开一个存在的工程。然后执行Editor > Clean Closure > Source Editor Command,需要确保在当前的文件里面含有一个闭包。这样就可以看到如下的效果,刚才开发的extension工作了!
Source Editor Command是命令默认的名字。开发者可以在extension的Info.plist文件里面修改。打开之后修改为 Clean Syntax。
当然,同样可以设置设置快捷键去自动调用Clean Syntax命令。打开Xcode的Preferences,选择Key Bindings 。搜索Clean Syntax,可以看到命令出现了,点击它的右边然后输入开发者想使用的快捷键,例如:Command-Alt-Shift-+。现在,回到源文件,然后使用快捷键就可以直接调用它了。
7. 技巧和窍门
Xcode 8和source editor extensions 依然在测试阶段。下面的一些方法可能会帮助开发者调试一些开发者遇到的问题。
如果开发者的extension在Xcode的测试实例中变得不可选择,杀掉com.apple.dt.Xcode.AttachToXPCService进程然后再次运行extension
仅重置缓存区里面修改过的行,这使得extension运行得更快而且不容易被Xcode杀掉进程。如果Xcode检测到开发者的extension花费太长时间的话有可能会杀掉它。
如果想集成多个命令,那就必须分配给每个指令不同的标识,然后使用XCSourceEditorCommandInvocation
对象的commandIdentifier
属性来识别用户触发的是哪一个。
总结
创建 Xcode source editor extension 是非常容易的。可以通过创建source editor extension去提高开发的效率,开始吧,动手去做。苹果公司引入了新的方式,广大开发者可以分享签名过的插件通过Mac App Store,这样一方面减轻了自己的工作,另一方面也可以让其他开发者从中受益。
该教程的代码地址: GitHub
原文地址
译者补充说明:目前Xcode8很不稳定,我自己在测试的时候Editor下面的命令选项时有时没有。还需要继续跟进和研究,
吐槽:不知道为何CSDN的markDown语法支持这么差,如果想看好点的排版看我博客吧使用 Xcode Source Editor Extension开发Xcode 8 插件