初涉Xcode插件开发

我二十几岁我好累.jpg,这几个月换完工作后都没时间学习提升了。这里整理一下近期新学到的一点点工程技术——Xcode插件开发。

Xcode插件开发

Xcode的插件目前有两种方式:

  1. Xcode Extension (官方发布的一种App Extension,基于XcodeKit框架实现,独立进程)
  2. Xcode Plugin(Xcode8以前的可直接支持的,目前是需要一些特殊手段才能加载运行起来)

第一种方式比较成体系的开发工具插件如支付宝的mPaas Xcode Extension它支持新建mPaaS工程、编辑工程(导入云端配置文件,添加删除mPaaS组件,升级mPaaS基线,生成无线保镖图片)、基础工具(工程打包工具等)。
特性:官方支持,主要用于源码文本编辑。
限制:以App Extension形式实际上是独立进程并不会影响Xcode进程,同时也无法获取Xcode的行为记录。

第二种方式是比较Hook的形式,这里有一些开源的插件可供参考。它是依赖于Xcode/Content/PlugIns启动这个插件,由于XCodeGhost等一系列的原因官方是已经禁用这种形式的私有插件,但我们仍可以某些手段绕开限制加载插件。
特性:以插件形式加载到Xcode进程,可以实现Xcode行为记录。
限制:非官方支持,无相关文档、需求较为小众、开发难度较大(需要dump相关的私有API),无法利用Xcode来发版。

如何加载一个Xcode Plugin

首先我们需要让Xcode拥有加载私有插件的能力。从开发者官网下载一个Xcode,然后从钥匙串创建一个自签证书。
-w540
接着用codesign对目标Xcode进行自签证书的重签:

1
2
# Xcodesigner就是自签证书的名字
sudo codesign -f -s XcodeSigner /Applications/Xcode_11.3.app

那么这个Xcode就拥有了加载私有Xcode Plugin的能力了,我们试加载Alcatraz这个插件试试。在加载之前我们还有做一个准备工作,就是要将Xcode_11.3DVTPlugInCompatibilityUUID放到Alcatraz.xcplugin/Contents/Info.plistDVTPlugInCompatibilityUUIDs数组里面才能够让插件支持这个版本的Xcode,而这个字段可以通过下面这条命令获取:

1
defaults read /Applications/Xcode_11.3.app/Contents/Info DVTPlugInCompatibilityUUID

我们只需要将这个插件手动放到Xcode/Content/PlugIns目录下然后重启Xcode点击Load Bundle就可以加载这个私有插件了。当在Xcode的导航栏Window下出现Package Manager这个选项的时候证明这个插件成功被加载出来了。
-w540
-w540

ps:私有插件也可以放到~/Library/Application Support/Developer/Shared/Xcode/Plug-ins这个目录下进行加载。

如何开发一个Xcode Plugin

这篇文档的主要目的是要是实现开发一个Xcode行为统计的插件,所以接下来开始研究一下如何开发这个功能。在开始之前我们首先需要一个插件工程模板,可以直接利用Xcode-Plugin-Template这个工程来实现,这个模板的安装可以通过上一步导入的Alcatraz插件管理器直接安装到这个Xcode上。
-w540
接着我们新建Project的时候就可以看到这个模板了。
-w540
下面我们创建一个名为XcodeTracker的插件工程,千万要记得给这个工程的Info.plist中加上这个版本Xcode的DVTPlugInCompatibilityUUID值才能加载私有插件出来。
-w720

默认编译出来的插件安装位置是放在~/Library/Application Support/Developer/Shared/Xcode/Plug-ins这个目录下面的。(这里有个小坑就是Debug这个插件的时候默认是会启动/Application/Xcode.app这个Xcode来调试,所以最好在开发插件的时候把你下载的那个Xcode_11.3重命名为Xcode)。
-w540
默认模板中的功能也十分简单,它实现在Xcode的Edit菜单下创建一个Do Action的按钮实现弹窗一个Hello World的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (BOOL)initialize {
// Create menu items, initialize UI, etc.
// Sample Menu Item:
NSMenuItem *menuItem = [[NSApp mainMenu] itemWithTitle:@"Edit"];
if (menuItem) {
[[menuItem submenu] addItem:[NSMenuItem separatorItem]];
NSMenuItem *actionMenuItem = [[NSMenuItem alloc] initWithTitle:@"Do Action" action:@selector(doMenuAction) keyEquivalent:@""];
//[actionMenuItem setKeyEquivalentModifierMask:NSAlphaShiftKeyMask | NSControlKeyMask];
[actionMenuItem setTarget:self];
[[menuItem submenu] addItem:actionMenuItem];
return YES;
} else {
return NO;
}
}

// Sample Action, for menu item:
- (void)doMenuAction {
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:@"Hello, World"];
[alert runModal];
}

-w720

参考

Xcode Plugins
手摸手带你玩转Xcode Extensions
iOS - Xcode Plugin 调研和使用
Xcode 4 插件制作入门