原文: iOS 10 Day By Day: Xcode Source Editor Extensions
译者:CocoaChina--CoderWQ
在 Xcode 8.0的版本里面,苹果引入了一个新的面向 macOS 的 App Extension,叫做 Xcode Source Editor(源代码编辑器)。通过这个源代码编辑器,我们也拥有了自己编写一个Xcode插件的能力 。此外,我们再也不用因为Xcode升级,来寻找各种各样让Alcatraz正常运行的办法了。
Xcode是苹果开发平台中一个让开发者又爱又恨的IDE,在使用过程中,我们发现Xcode的插件的数量一直供不应求。开发者可以添加一些功能做为内置功能的扩展。比如检查拼写或者转换所有的 UIColor 使用新的常量。
在Xcode8以前,Xcode是没有这个能力的。所以Alcatraz填补了这个空白,看上去仿佛Xcode的插件管理器就是 Alcatraz 。后来由于安全性跟稳定性的担忧,利用Xcode8做开发,我们再也不能使用Alcatraz了。幸运的是,苹果添加了新的源代码编辑器来扩展Xcode的插件功能。
我们创建一个extension来实现替换ASCII字符为表情的功能。举个例子:比如"That’s a nice thing to say :)"我们想变成That’s a nice thing to say“。往常,如果我们想实现这个功能,我们可能会怀着希望去Github找。我们往下面看:
像我在 Day1 中创建iMessage App那样,苹果这次推出的各类Extensions 跟Xcode Extensions是差不多的, 但这个官方的Xcdoe插件最大的有点就是可以上架Mac App Store。我们可以利用Extension来扩展整个程序,听上去有点夸张,其实它更像是一种配置选项。Xcode提供了一个没有UI的菜单选项来运行一个命令。
接下来我们创建一个新的MacOS的项目,名字是Emojificate。这里要说明一下,我们可以先忽略对Mac app的相关文件,专注于extension方面的事情。
我们需要创建一个新的target,选择的Xcode Extension,我们还需要取一个独一无二的名字,我这里使用的名字是Emojificator_Extension。
Xcode创建了我们需要的一切。
我们改变extension的名字,只需要设置Bundle name的key值为Emojificator。
像其他的扩展那样,我们可以在info.plist中定一些属性。我们展开NSExtension,在下面,我们可以定义extension的type(Xcode.extension.source-editor),以及插件的类名,key名为XCSourceEditorExtensionPrincipalClass ,大家会发现,在我们的这个项目中,类名是SourceEditorExtension。我们还可以定义扩展中,用户可以执行的命令。
你的扩展还可以包含大量的命令行,在info.plist中我们可以指定他们的顺序。在我们的项目中我们只有一个命令行,它被定义在EmojificatorCommand这个类里,并且包含了XCSourceEditorCommand的协议。如果想定义的这个类的名称,那么我们值需要修改XCSourceEditorCommandClassName的值就好了。
这个类并不限制运行命令行的个数,如果你有多个命令行需要执行,都可以放到同一个类中。如果有相同的方法,你还可以抽取出来公用,通常,我们区分不同的命令是通过标识(即XCSourceEditorCommandIdentifier这个key对应的值)来区分的。
最后,我们在简单说说,修改命令行的名称。在我们的项目中,XCSourceEditorCommandClassName所对应的值是Emojificate!,修改对应的key,我们就可以修改命令行的名称咯。
SourceEditorExtension.swift
这个文件我们必须遵守XCSourceEditorExtension这个协议。当我们设置好我们的plist文件后,我们不需要做任何事,系统就已经帮我们初始化好了。如果你想自定义初始化,你需要在extensionDidFinishLaunching这个方法来自定义初始化。
Xcode在启动时,扩展就会启动。在命令执行时,扩展只是执行。这么做的好处就是,快!
菜单项是动态加载的,可以实现 SourceEditorExtension 的 commandDefinitions: 实现,但是这会覆盖掉 Info.plist 中的定义。
举个例子,下面的代码会返回跟工程现在一样的配置。
var commandDefinitions: [[XCSourceEditorCommandDefinitionKey: AnyObject]] { // If your extension needs to return a collection of command definitions that differs from those in its Info.plist, implement this optional property getter. return [[ .classNameKey : "Emojificator_Extension.EmojificateCommand", .identifierKey : "com.shinobicontrols.Emojificator_Extension.EmojificateCommand", .nameKey : "Emojificate!" ]] }
通常,Xcode会创建一个SourceEditorCommand的类,不过在这里,我重新命名成EmojificateCommand了。为了保证跟系统的一致,我们要保证它遵守XCSourceEditorComman
的协议。
import Foundation import XcodeKit class EmojificateCommand: NSObject, XCSourceEditorCommand { }
举个例子,我们定义了一组表情跟ASCII之间的映射,并且只是用定义好的这些表情。
let asciiToEmojiMap = [ ":)" : ":)", ";)" : ";)", ":(" : ":("] *Note: Use actual emojis in second column
为了实现把ASCII字符替换成Emoji表情,我们要用2个方法
extension EmojificateCommand { /// Returns whether the string contains an item that can be converted into emoji func replaceableItemsExist(in string: String) -> Bool { for asciiItem in asciiToEmojiMap.keys { if string.contains(asciiItem) { return true } } return false } /// Replaces any ASCII items with their emoji counterparts and returns the newly 'emojified' string func replaceASCIIWithEmoji(in string: String) -> String { var line = string for asciiItem in asciiToEmojiMap.keys { line = line.replacingOccurrences(of: asciiItem, with: asciiToEmojiMap[asciiItem]!) } return line } }
最后,我们在.m文件中,实现`perform(with: completionHandler:)`方法,来在xcode命令行执行的时候来调用它。
func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: (Error?) -> Void) { let lines = invocation.buffer.lines for (index, line) in lines.enumerated() { if let line = line as? String, replaceableItemsExist(in: line) { lines[index] = replaceASCIIWithEmoji(in: line) } } // Command must perform completion to signify it has completed completionHandler(nil) }
在`perform(with: completionHandler:)`方法中,我们有一个invocation变量,类型是XCSourceEditorCommandInvocation,它是命令调用的文件内容,比如.swift文件,oc头文件,或者空格等字符。我们要做的就是在buffer中根据当前行,获取我们要的数据。我们还可以使用`completeBuffer`,它可以是字符串或者数组。如果只是简单的修改一些字符,苹果建议使用数组来处理,因为Xcode对数组的处理性能更为好。
最后我们调用completionHandler(nil),告诉Xcode命令执行完毕。
Xcode Extension是在一个单独的进程中运行,这真是极好的,这意味着缓慢执行的命令行并不会影响到Xcode本身的使用。另一件值得注意的,Xcode对命令运行的时间要求极为严格,如果没有在很短时间内完成,你的扩展将会被"点名批评",同时用户也可以选择取消命令。
这有一点跟我们平常开发不一样的是,Xcode实在测试版本中来测试我们的命令。这个测试版本的Xcode的图标颜色是灰色的,这有助于我们区分开发的内容。
下面是执行的效果图:
苹果的这项举措必定会大大扩展开发者的生态圈,也会提高了开发者的效率。这些命令的执行,默认仅允许修改一些文件的buffer和沙盒。如果你需要,你可以请求更高的权限。据我所知,未来Xcode工程师会让这个工具更加强大,所以如果你有一些不能实现的特定目标,还可以将你的用例提交给苹果公司。
详情请见 WWDC视频 的介绍。