Xcode 插件开发
我最近一年来都在开发ios应用,不过感觉公司的app维护起来非常麻烦。
因为公司要为很多个企业订做app,每个app的功能基本相同,只是界面上的一些图片和文字要换掉,功能也有一些小改动。考虑到代码维护的问题,比较好的做法就是只维护一份代码,然后用不同的配置文件来管理各个target的内容。
当工程里达到上百个target的时候,为工程新增文件就成了一件非常痛苦的事情。
我必须一个一个地去勾选所有的targets,往往要花上几分钟的时间来重复无聊的操作,既浪费时间又影响心情,而Xcode居然没有自带全选targets的功能。因此我萌生了一个想法:写一个能自动勾选所有targets的插件。
google一下Xcode的制作教程,找到了VVDocumenter插件作者写的一篇教程:《Xcode 4 插件制作入门》。
这篇教程很适合入门,不过里面有些东西由于年代久远,已经不兼容最新的Xcode 6.1了。但是教程里很多细节都写得很详细,建议先看完这篇教程。我看了教程后加上自己的摸索,终于完成了插件的开发,因此在这里把插件的开发过程分享出来。
本插件的源码下载地址:https://github.com/poboke/AllTargets
一、安装插件模板
Alcatraz是一款开源的Xcode包管理器,源码下载地址为:https://github.com/supermarin/Alcatraz
编译完成之后,重启Xcode,然后点击Xcode顶部菜单”Windows”中的”Package Manager”就可以打开Alcatraz包管理器面板。
搜索关键字”Xcode Plugin”,可以找到一个”Xcode Plugin”模板,该模板可以用来创建Xcode 6+的插件。
点击左边的图标按钮就可以把模板安装到Xcode里。
新建一个Xcode工程,选择”Xcode Plugin”模板,本例子的工程名为AllTargets。
该模板的部分初始代码为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
- (id)initWithBundle:(NSBundle *)plugin { if (self = [ super init]) { // reference to plugin's bundle, for resource access self.bundle = plugin; // 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 setTarget:self]; [[menuItem submenu] addItem:actionMenuItem]; } } return self; } // Sample Action, for menu item: - (void)doMenuAction { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@ "Hello, World" ]; [alert runModal]; } |
初始代码会在Xcode的”Edit”菜单里加入一个名字为”Do Action”的子菜单,当你点击这个子菜单的时候,会调用doMenuAction函数弹出一个提示框,提示内容为”Hello, World”。
二、需求分析
在Xcode里按command+alt+A打开添加文件窗口:
所有的targets都位于白色矩形视图里,可以猜测该矩形视图是一个NSTableView(大小差不多为320*170),勾选的按钮是一个NSCell。
首先要获得NSTableView对象,《Xcode 4 插件制作入门》里提到可以使用递归打印subviews的方法来得到某个NSView对象。
不过我发现一种更简便的方法,在本例子中比较适用。在没打开添加文件窗口之前,NSTableView是不会创建的,而视图创建设置尺寸时都会调用NSViewDidUpdateTrackingAreasNotification通知。所以我们可以先监听该通知,再打开添加文件窗口,这样就能得到添加文件窗口里所有视图对象了,修改代码为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
- (void)doMenuAction { //监听视图更新区域大小的通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationListener:) name:NSViewDidUpdateTrackingAreasNotification object:nil]; } - (void)notificationListener:(NSNotification *)notification { //打印出视图对象以及视图的大小 NSView *view = notification.object; if ([view respondsToSelector:@selector(frame)]) { NSLog(@ "view : %@, frame : %@" , view, [NSValue valueWithRect:view.frame]); } } |
编译代码后重启Xcode,打开控制台(Control+空格,输入console),并清空控制台里的log。
点击Xcode的”Do Action”子菜单开始监听消息,这时打开添加文件的窗口会看到控制台输出一堆log。
把log复制到MacVim里,搜索”NSTableView”,可以找到一条结果:
1
|
view : < NSTableView: 0x7fb206c65f40>, frame : NSRect: {{0, 0}, {321, 170}} |
可以发现,此TableView的大小为321*170,看来正是我们正在寻找的对象。
三、hook私有类
由于NSCell的值是由NSTableView的数据源所控制的,所以我们必须找到NSTableView的数据源,修改一下代码打印出数据源:
1
2
3
4
5
6
7
8
|
- (void)notificationListener:(NSNotification *)notification { NSView *view = notification.object; if ([view.className isEqualToString:@ "NSTableView" ]) { NSTableView *tableView = (NSTableView *)view; NSLog(@ "dataSource : %@" , tableView.dataSource); } } |
可以看到控制台输出了log:
1
|
dataSource : < Xcode3TargetMembershipDataSource: 0x7fadb7352830> |
Xcode3TargetMembershipDataSource是Xcode的私有类,位于 /Applications/Xcode.app/Contents/PlugIns/Xcode3UI.ideplugin/Contents/MacOS/Xcode3UI 里。由于这个私有类没有frameworks可引用,所以只能通过NSClassFromString来Hook该私有类的函数。
在这里可以下载从Xcode 6.1 dump出来的私有类头文件:https://github.com/luisobo/Xcode-RuntimeHeaders/tree/xcode6-beta1。
打开Xcode3TargetMembershipDataSource.h,部分代码如下:
1
2
3
4
5
6
7
|
@interface Xcode3TargetMembershipDataSource : NSObject { NSMutableArray *_wrappedTargets; //...... } - (void)updateTargets; //...... |
_wrappedTargets数组很有可能保存着targets的信息,updateTargets函数的作用应该是用来更新targets的值,所以可以试试hook updateTargets函数,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
//originalImp用来保存原私有类的方法 static IMP originalImp = NULL; @implementation AllTargets //...... - (void)doMenuAction { [self hookClass]; } - (void)hookMethod { SEL method = @selector(updateTargets); //获取私有类的函数 Class originalClass = NSClassFromString(@ "Xcode3TargetMembershipDataSource" ); Method originalMethod = class_getInstanceMethod(originalClass, method); originalImp = method_getImplementation(originalMethod); //获取当前类的函数 Class replacedClass = self.class; Method replacedMethod = class_getInstanceMethod(replacedClass, method); //交换两个函数 method_exchangeImplementations(originalMethod, replacedMethod); } - (void)updateTargets { //先调用原私有类的函数 originalImp(); //查看_wrappedTargets数组里保存了什么类型的对象 NSMutableArray *wrappedTargets = [self valueForKey:@ "wrappedTargets" ]; for (id wrappedTarget in wrappedTargets) { NSLog(@ "target : %@" , wrappedTarget); } } |
可以看到控制台输出了log,由于工程只有一个target,所以只有一个对象:
1
|
target : < Xcode3TargetWrapper: 0x7f8b59264ab0> |
在Xcode的私有类里找到Xcode3TargetWrapper.h,内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@interface Xcode3TargetWrapper : NSObject { PBXTarget *_pbxTarget; Xcode3Project *_project; NSString *_name; NSImage *_image; BOOL _selected; } @property(readonly) NSImage *image; // @synthesize image=_image; @property(readonly) NSString *name; // @synthesize name=_name; @property BOOL selected; // @synthesize selected=_selected; //...... |
可以看到,该类有三个属性:图片、名字和是否选中,我们只要把selected属性改为YES就行了。
我们把updateTargets函数修改为:
1
2
3
4
5
6
7
8
9
10
11
|
- (void)updateTargets { //先调用原私有类的函数 originalImp(); //修改wrappedTarget的属性 NSMutableArray *wrappedTargets = [self valueForKey:@ "wrappedTargets" ]; for (id wrappedTarget in wrappedTargets) { [wrappedTarget setValue:@YES forKey:@ "selected" ]; } } |
再次编译重启Xcode,打开添加文件窗口,可以发现所有targets都自动选中了。
四、添加菜单
考虑到有时可能要关闭这个功能,所以可以给菜单加上是否选中的状态,此外还可以给Xcode加上一个独立的Plugins菜单,大部分插件就可以放在这个菜单里,以方便管理。
创建菜单的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
- (void)addPluginsMenu { //增加一个"Plugins"菜单到"Window"菜单前面 NSMenu *mainMenu = [NSApp mainMenu]; NSMenuItem *pluginsMenuItem = [mainMenu itemWithTitle:@ "Plugins" ]; if (!pluginsMenuItem) { pluginsMenuItem = [[NSMenuItem alloc] init]; pluginsMenuItem.title = @ "Plugins" ; pluginsMenuItem.submenu = [[NSMenu alloc] initWithTitle:pluginsMenuItem.title]; NSInteger windowIndex = [mainMenu indexOfItemWithTitle:@ "Window" ]; [mainMenu insertItem:pluginsMenuItem atIndex:windowIndex]; } //添加"Auto Select All Targets"子菜单 NSMenuItem *subItem = [[NSMenuItem alloc] init]; subItem.title = @ "Auto Select All Targets" ; subItem.target = self; subItem.action = @selector(toggleMenu:); subItem.state = NSOnState; [pluginsMenuItem.submenu addItem:subItem]; } - (void)toggleMenu:(NSMenuItem *)menuItem { //改变菜单选中状态 menuItem.state = !menuItem.state; //重新交换函数,hook与unhook [self hookMethod]; } |
本插件的源码下载地址:https://github.com/poboke/AllTargets
Xcode 插件开发的更多相关文章
- Xcode插件开发案例教程
引言 在平时开发过程中我们使用了很多的Xcode插件,虽然官方对于插件制作没有提供任何支持,但是加载三方的插件,默认还是被允许的.第三方的插件,存放在 ~/Library/Application Su ...
- Xcode插件开发
一.安装模板 1.git clone https://github.com/kattrali/Xcode-Plugin-Template.git 2.cd Xcode-Plugin-Template ...
- Xcode7插件开发:从开发到拉到恶魔岛
Xcode很强大,但是有些封闭,官方并没有提供Xcode插件开发的文档.喵神的教程比较全,也比较适合入门.本文的教程只是作为我在开发FKConsole的过程中的总结,并不会很全面. FKConsole ...
- ios最新的视频地址链接
2016年最新iOS教程UI基础http://pan.baidu.com/s/1pLvnH8n资料链接:http://pan.baidu.com/s/1nvewKkh 密码:wktp 2016年最新i ...
- iOS_高效开发之道
iOS_高效开发之道 话不多说, 总结一下个人感觉有利于提高iOS开发效率的几个小技巧. 本文将从下面几方面介绍: Xcode经常使用快捷键 Xcode调试技巧 Objc经常使用代码片段 Xcode插 ...
- Final Cut Pro X效果插件开发总结
一.介绍 最近公司需要针对Final Cut Pro(FCP)开发一款效果插件,用于对公司自己开发的视频格式进行后期处理.Final Cut Pro是苹果公司推出的一款视频剪辑软件,因此需要在OSX平 ...
- 用 Xcode 开发 Cydia Substrate 插件(一)
关于这方面的中文资料太少了,以至于可能很多对插件开发感兴趣的孩子们都不知从何下手,于是呢我就写了这篇文章,希望对你能有所帮助.如果你觉得文章内容有什么错误呢也请提出来. 准备开发环境 1. 从 App ...
- Xcode升级后插件失效的原理与修复办法
转载:http://joeshang.github.io/2015/04/10/fix-xcode-upgrade-plugin-invalid/ Xcode 的插件大大丰富了 Xcode 的功能,而 ...
- Xcode 4 插件制作入门
转自:http://www.onevcat.com/2013/02/xcode-plugin/ 2014.5.4更新 对于 Xcode 5,本文有些地方显得过时了.Xcode 5 现在已经全面转向了 ...
随机推荐
- 会话数据的保存——cookie
会话的理解 可以简单的理解为:用户打开浏览器,访问多个web资源,然后关闭浏览器,这个过程可以称为一次会话 有状态会话:可以简单理解为一个同学来了这个教室,下一次再来我们知道他来过这个教室,我们可以称 ...
- Ajax 整理总结(入门)
Ajax 学习要点: 1.Ajax 概述 2.load()方法 3.$.get()和$.post() 4.$.getScript()和$.getJSON() 5.$.ajax()方法 6.表单序列化 ...
- java 图片压缩 剪切 水印 转换 黑白 缩放
专注java已6年,欢迎加入java核心技术QQ群:135138817,每周五晚有群主进行技术讲座. import java.awt.AlphaComposite; import java.awt.C ...
- 用Gitosis搭建Git服务器(经典资料)
该文档介绍了用Gitosis自己搭建Git服务器,文章来自于<Git权威指南>一书的第31章,讲述详细易懂易操作,是搭建Git服务器绝好资料! 下载地址:http://download.c ...
- DTrace to Troubleshoot Java Native Memory Problems
How to Use DTrace to Troubleshoot Java Native Memory Problems on Oracle Solaris 11 Hands-On Labs of ...
- End-to-End Tracing of Ajax/Java Applications Using DTrace
End-to-End Tracing of Ajax/Java Applications Using DTrace By Amit Hurvitz, July 2007 Aja ...
- oracle用户管理实例
oracle中的用户角色分为预定义角色和自定义角色. 角色是把常用的权限集中起来形成角色. 授权/分配角色命令 grant 权限/角色 to 用户 收回权限命令: revoke 综合案例: 创建一个用 ...
- 购买SSD固态硬盘须当心,你知道什么是SLC、 MLC、TLC闪存芯片颗粒吗?
固态硬盘凭借其存取速率超快等自身优势,被越来越多的电脑爱好者所青睐,并迅速普及到了广大用户的电脑中,因为固态硬盘与传统机械硬盘相比,确实在运行效率等方面有了质的提升,但是亦是美网络小编要提醒大家的是, ...
- Hadoop书籍汇总
<Hadoop实战>陆嘉恒 <Hadoop - The Definitive Guide>Tom White,中文版<Hadoop权威指南> <Hadoop技 ...
- ASP.NET中常用方法
身份证号检测: /// <summary> /// 检验身份证号是否正确 /// </summary> /// <param name="Id"> ...