开发一个简单的Pod Install 插件

前几天刚刚粗略了学习了一下Xcode的插件开发,一时心痒,就准备做个简单的插件练练手。

哎哟我擦,正当我准备大展身手的时候,我突然想到我该做个啥呢? 我这真是有了程序员,只差一个好Idea了。

好吧,正巧这个时候,我发现我和朋友协作的一个iOS项目在两个分支上同步开发,每次要合并后拉下分支,都发现Pod.lock文件都产生了变化,无法编译成功。每当这个时候,我都要进入terminal输入一大堆的cd ..进入对应的文件目录执行pod install命令,甚是繁琐。

当然啦,你可以通过alias 配置快速的执行命令,但是,你仍然得切换出Xcode的窗口,对于我们这种效率控来说不能接受。

所以,我就想到了,在Xcode中利用插件集成一下关于pod的一些功能,同时绑定快捷键提高操作效率。

说干就干。

首先我们利用Xcode的plugin template生成项目的一些基本流程结构。关于插件的具体思路可以参考我之前的一篇文章《DXXcodeConsoleUnicodePlugin源码解析》

在这里,我们着重介绍一下利用 NSTask 去执行诸如pod install这样的命令。

实现思路

在实现真正的Objective-C代码之前,我们首先现在terminal中随便找个安全的目录敲入pod install来试试看结果,如下所说:

pod install
[!] No `Podfile' found in the project directory.

从这个错误提示中我们可以大致了解,pod install的命令依赖于所谓的Podfile。于是,我们输入pod install --help查看其对应的帮助手册:

--project-directory=/project/dir/   The path to the root of the project
                                       directory
--no-clean                          Leave SCM dirs like `.git` and `.svn`
                                       intact after downloading
--no-integrate                      Skip integration of the Pods libraries
                                       in the Xcode project(s)
--no-repo-update                    Skip running `pod repo update` before
                                       install
--silent                            Show nothing
--verbose                           Show more debugging information
--no-ansi                           Show output without ANSI codes
--help                              Show help banner of specified command

从第一条帮助命令张,我们可以看到,我们需要通过–project-directory=来设置pod install的根目录,也即Podfile的所在。

好,事情到这里,我们在编写插件前需要的准备工作就基本完成了,我们现在只需利用NSTask将我们在命令行中输入的命令执行即可。

让我们来看看实现的代码:

 // 1.
 [self searchMainProjectPath];

 // 2.
 NSTask *podInstallAction = [[NSTask alloc] init];
 podInstallAction.currentDirectoryPath = self.mainProjectPath;
 podInstallAction.arguments = @[@"install"];
 podInstallAction.launchPath = @"/usr/bin/pod";

 // 3.
 NSPipe *pipeOut = [NSPipe pipe];
 [podInstallAction setStandardOutput:pipeOut];
 NSFileHandle *output = [pipeOut fileHandleForReading];

 [output setReadabilityHandler:^(NSFileHandle * _Nonnull fileHandler) {
   NSData *data = [fileHandler availableData];
   NSString *text = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];

   NSLog(@"text is %@", text);
 }];

[podInstallAction launch];
[podInstallAction waitUntilExit];
  1. 我们首先要寻找到当前项目的主目录,也就是Podfile的路径
  2. 然后我们构建NSTask,将其的执行目录设置成我们的主目录,然后/usr/bin中调出pod的可执行文件,执行pod install
  3. 我们利用NSPipe将默认的NSTask的输出(stdout)重定向到我们的指定的地方,这样有助于我们查看log或者进行流程工程。

到这里,基本上一个简单的小插件就完成了,但是我在这里想要强调一点关于主工程路径搜索的一些问题,我们首先来看代码:

NSArray *workspaceWindowControllers = [NSClassFromString(@"IDEWorkspaceWindowController") workspaceWindowControllers];
[workspaceWindowControllers enumerateObjectsUsingBlock:^(id controller, NSUInteger idx, BOOL *stop) {
  if ([[controller valueForKey:@"window"] isMainWindow]) {
    id workspace = [controller valueForKey:@"_workspace"];
    NSString *filePath = [[workspace valueForKey:@"representingFilePath"] valueForKey:@"pathString"];
    NSString *projectName = [[filePath lastPathComponent] stringByDeletingPathExtension];
    NSLog(@"CocoaPodUI::ProjectName::%@", projectName);

    NSString *text = [[filePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"Podfile"];

    self.mainProjectPath = [filePath stringByDeletingLastPathComponent];

    NSLog(@"pod ifle is %@", text);
  }
}];

基于Xcode的插件开发实际上利用了大量的私有头文件。由于Objective-C著名的runtime特性,因此,很多时候,我们可以利用key-value-coding的方式获取我们普通途径下无法得到的结果。
同时,当有一个类的方法是私有方法的时候,你可以利用一个category声明同样的函数签名,不需要实现,Objective-C的runtime会自动帮你转发对应的message passing