其实官方还是宣扬了一些优点的,其中最显著的莫过于可以上架 Mac App Store,但这点优点实在是无法弥补他的弱鸡。他能做的事情几句话就可以说完,开发一个这样的插件是极其的简单。简单说吧,一个插件能做下面的事情:
- 获取 Xcode 正在编辑的文本
- 获取所有的选中区域
- 替换 Xcode 正在编辑的文本
- 选中 Xcode 正在编辑的文本
- 在 Xcode 的 Editor 菜单里面给你的插件生成一个子菜单,用于调用插件
- 可以在 Xcode 的 Key Binding 里面给插件分配一个快捷键
没了,这就是他全部能做的事情,下面我通过一个选中文字转换成 Base64 编码的例子来简单介绍一下。
你需要在一个现有的或者新建的 macOS 项目里面新建一个 Xcode Source Editor Extension target,然后理论上这个时候已经可以有插件的效果了,但是今天我试了很多次就是不行,其实没有什么特别的原因,就是因为 Xcode 目前的阶段极其不稳定,我没写错任何东西。
有几个核心的观念要介绍一下,首先,实现了 XCSourceEditorExtension 接口的类,用于管理插件的生命周期或者对 Command 进行动态配置,说的很牛逼其实一共两个方法。
- (void)extensionDidFinishLaunching
{
// If your extension needs to do any work at launch, implement this optional method.
}
- (NSArray <NSDictionary <XCSourceEditorCommandDefinitionKey, id> *> *)commandDefinitions
{
// 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 @[];
}
分别是在插件成功加载后调用和动态配置 Command,但是 Command 也可以静态的配置在插件的 Plist 上面,我的例子里面其实就是这么一个结构:
<dict>
<key>XCSourceEditorCommandDefinitions</key>
<array>
<dict>
<key>XCSourceEditorCommandClassName</key>
<string>SourceEditorCommand</string>
<key>XCSourceEditorCommandIdentifier</key>
<string>app.cyan.XcodeExtension.xEncoders.SourceEditorCommand</string>
<key>XCSourceEditorCommandName</key>
<string>Base64 Encode</string>
</dict>
</array>
<key>XCSourceEditorExtensionPrincipalClass</key>
<string>SourceEditorExtension</string>
</dict>
没什么好解释的,指定了实现类的名称和菜单的名称。
XCSourceEditorCommand 是另外一个核心概念,其实就是当插件被触发之后,你有机会在代理方法里面拦截到这个消息(XCSourceEditorCommandInvocation),做出处理之后将内容返回给 Xcode,仅此而已。
XCSourceEditorCommandInvocation 里面会存放一些 meta 数据,其中最重要的是 identifier 和 buffer,identifier 就是用来做区分的,buffer 则是整个插件中最最重要的概念,但是他其实也很简单。
XCSourceTextBuffer 最重要的环节有两个,
/** The lines of text in the buffer, including line endings. Line breaks within a single buffer are expected to be consistent. Adding a "line" that itself contains line breaks will actually modify the array as well, changing its count, such that each line added is a separate element. */
@property (readonly, strong) NSMutableArray <NSString *> *lines;
/** The text selections in the buffer; an empty range represents an insertion point. Modifying the lines of text in the buffer will automatically update the selections to match. */
@property (readonly, strong) NSMutableArray <XCSourceTextRange *> *selections;
分别表示了传递过来的文本,以及选中区域。所以要做的事情已经一目了然了,流程如下:
- 在菜单或快捷键触发插件
- XCSourceEditorCommand 拦截了消息
- 从 invocation 中拿到 buffer
- 在 buffer 中根据当前行,获取到你要的数据
- 把数据替换后塞回去
所以这个 Base64 的例子,最后就只有这么几行代码(只处理一行文本的情况下)
static inline NSString *Base64Encode(NSString *string) {
NSData *encodedData = [string dataUsingEncoding:NSUTF8StringEncoding];
return [encodedData base64EncodedStringWithOptions:0];
}
- (void)performCommandWithInvocation:(XCSourceEditorCommandInvocation *)invocation completionHandler:(void (^)(NSError * _Nullable nilOrError))completionHandler {
XCSourceTextRange *selection = invocation.buffer.selections.firstObject;
NSInteger index = selection.start.line;
NSString *text = invocation.buffer.lines[index];
invocation.buffer.lines[index] = Base64Encode(text);
completionHandler(nil);
}
最后把插件编译运行到 Xcode 8 beta 上面,会出现一个被调试的 Xcode,是黑色的:
然后随便选个项目跑起来,菜单里面就有了插件了
我说的轻巧,其实现实中(目前 beta1),出现菜单的概率非常的低,这让我非常恼火。
当然这个插件开发模式的确能拿来做一些事情,比如代码的格式化,或者是编解码,总之都集中在 文本的处理体验上面。但他的局限性也是非常的明显,例如:
- 没有 UI 相关的接口,纯粹只能做文本处理
- 没办法直接绑定快捷键,还得到 Xcode 的设置里面去设置
- 就目前而言,调试的成功率实在是太低了,很郁闷
- 只能通过菜单或快捷键调用
- 只能处理编辑区域
这些和第三方的 Xcode 插件比起来要差的很远,不过这还算是一个不错的开端,希望在日后可以把更多的接口开放出来,让 Xcode 这个本来就让人恼火的东西变得更好用。