Xcode Source Editor Extension
不知道是不是受xcodeghost的影响,现在任何library和bundle都需要Xcode8认证许可,通过注入code实现第三方功能的plugin也不允许在Xcode8上使用。取而代之的,Apple提供了一种新技术,通过为Xcode开发扩展,获取并改变开发环境,从而实现类似plugin的功能。这个技术就是Xcode Source Editor Extension(以下简称xsee)。
要指出的是,xsee虽然打开了一条新道路,但作为新技术,目前仍然是不成熟并且功能也很有限。
- xsee只能获取和修改source code,并不能像很多功能强大的插件,对整个工程作出修改。
- 本质是extension,必须依存于MacOS app
- 没有自己单独的UI interface
- 只能通过Xcode command方式触发
不过既然提供了一种新技术,想必Apple也会在这个方向上有所深入,未来应该会提供更多更全面的功能。
Demo: 类似VVDocumenter为方法添加注释
以下通过实现一个类似VVDocumenter的demo,来介绍如何创建使用xsee。
-
新建MacOS app
xsee本质是mac extension,所以需要新建一个MacOS app。
create MacOS app.png -
创建extension
file -> new -> target 创建Xcode Source Editor Extension
create xsee.png -
设置并运行
编辑extension的scheme,设置executable为Xcode.app,即mac上安装的Xcode8。
set extension.png
运行extension,就会生成一个自定义Xcode环境,在菜单栏的Editor下,可以看到一个新的command,点击该command会触发扩展功能。
customer xcode.png -
分析code structure
新建的extension结构很简单,默认创建几个文件:
Info.plist
SourceEditorCommand.h
SourceEditorCommand.m
SourceEditorExtension.h
SourceEditorExtension.m
SourceEditorExtension中定义了extension的life cycle,虽然只有一个launch 函数。command信息无需在commandDefinitions中设定,可以直接到info.plist中设置:
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>XCSourceEditorCommandDefinitions</key>
<array>
<dict>
<key>XCSourceEditorCommandClassName</key>
<string>SourceEditorCommand</string>
<key>XCSourceEditorCommandIdentifier</key>
<string>HAC.addDocuments</string>
<key>XCSourceEditorCommandName</key>
<string>Sakura</string>
</dict>
</array>
<key>XCSourceEditorExtensionPrincipalClass</key>
<string>SourceEditorExtension</string>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.dt.Xcode.extension.source-editor</string>
</dict>
SourceEditorCommand中定义了command触发的回调函数,具体的处理逻辑放在
- (void)performCommandWithInvocation:(XCSourceEditorCommandInvocation *)invocation completionHandler:(void (^)(NSError * _Nullable nilOrError))completionHandler
{
// Implement your command here, invoking the completion handler when done. Pass it nil on success, and an NSError on failure.
[HACExtensionManager handleInvocation:invocation];
completionHandler(nil);
}
方法传入的参数XCSourceEditorCommandInvocation即保存着当前source code的所有信息,最重要的是identifier和buffer,前者即定义在info.plist中的command id,做区分;buffer即缓存的环境信息,不过目前信息很少,重要的一个是lines一个是section
/** 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;
lines是source code的行信息,selection是当前选中区域。然后就没了...
所以整个流程很简单:
- 启动extension,回调extensionDidFinishLaunching
- 菜单或快捷键触发command
- 拦截command,调用performCommandWithInvocation
- 获取当前环境信息XCSourceEditorCommandInvocation,处理并回塞数据到buffer
- 刷新Xcode环境
在本demo中,逻辑很简单:
- 取得当前选中区后面的文本
- 遍历匹配最接近的一个方法
- 正则表达式解析出方法名,返回类型,参数
- 生成注释信息
- 将注释信息回塞到选中区后一行。
tips
如果在OS 10.11上运行,可能需要运行命令:
sudo /usr/libexec/xpccachectl
并且在 Xcode 尝试加载扩展之前重启。这是因为安装新的 SDK 以及 El Capitan 的 XPC 服务不允许这样的操作。
参考资料时,很多人反映extension性能还很不稳定。目前在Xcode8正式版本上,感觉还是很可靠的,当然目前只是初步体验,demo很简单。