Xcode Source Editor Extension

简介: 在 Xcode 8.0 的 beta 里面,Apple 引入了一个新的面向 macOS 的 App Extension,叫做 Xcode Source Editor Extension(以下简称插件),今天看到一个提问说是这个怎么弄,我简单看了一下 WWDC 的 Session:https://developer.apple.com/videos/play/wwdc2016/414/ 现在总结在这
在 Xcode 8.0 的 beta 里面,Apple 引入了一个新的面向 macOS 的 App Extension,叫做 Xcode Source Editor Extension(以下简称插件),今天看到一个提问说是这个怎么弄,我简单看了一下 WWDC 的 Session: developer.apple.com/vid 现在总结在这里。首先我要说一下结论: 我对这个所谓的官方 Xcode 插件开发接口,非常的失望

其实官方还是宣扬了一些优点的,其中最显著的莫过于可以上架 Mac App Store,但这点优点实在是无法弥补他的弱鸡。他能做的事情几句话就可以说完,开发一个这样的插件是极其的简单。简单说吧,一个插件能做下面的事情:

  1. 获取 Xcode 正在编辑的文本
  2. 获取所有的选中区域
  3. 替换 Xcode 正在编辑的文本
  4. 选中 Xcode 正在编辑的文本
  5. 在 Xcode 的 Editor 菜单里面给你的插件生成一个子菜单,用于调用插件
  6. 可以在 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;
分别表示了传递过来的文本,以及选中区域。所以要做的事情已经一目了然了,流程如下:
  1. 在菜单或快捷键触发插件
  2. XCSourceEditorCommand 拦截了消息
  3. 从 invocation 中拿到 buffer
  4. 在 buffer 中根据当前行,获取到你要的数据
  5. 把数据替换后塞回去

 所以这个 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 这个本来就让人恼火的东西变得更好用。



目录
相关文章
|
7月前
|
jenkins Unix 持续交付
个人记录jenkins编译ios过程 xcode是9.4.1
个人记录jenkins编译ios过程 xcode是9.4.1
108 2
|
7月前
|
Linux 数据安全/隐私保护 iOS开发
如何使用 Xcode 打包导出 IPA 文件并进行 iOS 应用内测,无需支付苹果开发者账号费用?
如何使用 Xcode 打包导出 IPA 文件并进行 iOS 应用内测,无需支付苹果开发者账号费用?
|
4月前
|
iOS开发 MacOS Perl
解决Xcode运行IOS报错:redefinition of module ‘Firebase‘和could not build module ‘CoreFoundation‘
解决Xcode运行IOS报错:redefinition of module ‘Firebase‘和could not build module ‘CoreFoundation‘
159 4
|
4月前
|
iOS开发 开发者
解决xcode doesn‘t support iphone’s ios 14.6 (18f72)
解决xcode doesn‘t support iphone’s ios 14.6 (18f72)
282 3
|
4月前
|
iOS开发
mac不通过Xcode直接打开IOS模拟器
mac不通过Xcode直接打开IOS模拟器
242 2
|
4月前
|
缓存 iOS开发
如何在Xcode删除某个版本的IOS模拟器
如何在Xcode删除某个版本的IOS模拟器
622 1
|
6月前
|
iOS开发
技术好文:xcode动态图,ios实现动态图,iosgif,暂停和继续播放
技术好文:xcode动态图,ios实现动态图,iosgif,暂停和继续播放
59 1
|
7月前
|
存储 定位技术 iOS开发
XCode8升级到Xcode9(操作系统为iOS11)后原来的工程中遇到的问题
XCode8升级到Xcode9(操作系统为iOS11)后原来的工程中遇到的问题
113 0
|
7月前
|
Linux 数据安全/隐私保护 iOS开发
Xcode8.1如何支持iOS8.0以下版本
Xcode8.1如何支持iOS8.0以下版本
47 0