Xcode7插件开发:从开发到拉到恶魔岛
Xcode很强大,但是有些封闭,官方并没有提供Xcode插件开发的文档。喵神的教程比较全,也比较适合入门。本文的教程只是作为我在开发FKConsole
的过程中的总结,并不会很全面。
FKConsole
是我开发的一个用于在Xcode控制台显示中文的插件,很小,很简单。这个插件开发的初衷是因为一个朋友有这种需求,而又没有找到相应的插件。如果不使用插件,就要在工程中嵌入文件,他并不乐意。所以FKConsole
在设计上只会去修改Xcode控制台内的文字显示,绝不会去修改你的文件,这点大家可以放心。
模板
因为现在已经有很多人做Xcode插件开发了,所以插件模板这种东西也就应运而生了。
Xcode-Plugin-Template是一个Xcode插件开发的基本模板,可以使用Alcatraz直接安装,支持Xcode 6+。
安装完成之后,在创建工程的时候,会出现一个Xcode的插件的选项,这个就是Xcode中的插件工程模板。
模板会生成NSObject_Extension
和你的工程名称一样的两个文件(.M)。
NSObject_Extension.m
中的+(无效)pluginDidLoad:(*一个NSBundle)插件
方法也是整个插件的入口。
一般来说,我们希望我们的插件是存活于整个Xcode的生命周期的,所以一般是一个单例,这个在另一个文件中会有体现。
添加按钮
这篇博文是记录FKConsole
开发过程的,自然以此举例。
Xcode启动之后,会发出NSApplicationDidFinishLaunchingNotification
的通知,模板上已经做了监听,我们在程序启动之后要在头部工具栏上加一个FKConsole
的选项,以设置FKConsole
插件的开关。
Mac软件开发和iOS开发有一些不同,它使用的是AppKit
的UI库,而不是UIKit
,所以可能会感觉有些别扭。
NSApp表示
中的[NSApp表示mainMenu]
方法可以获取到头部的主按钮,里面会包含很有NSMenuItem
,我们将在Xcode的Window
选项之前插入一个Plugins
选项(参考破博客的做法),然后在这个选项中添加一个FKConsole
的选项。(之所以添加一个Plugins
选项是因为有些插件会添加到Edit
中,有些会添加到View
、Window
中,我找半天都没找到选项在哪,还不如直接建一个Plugins
选项,用户一眼就能知道插件在哪。)
NSMenu * MAINMENU = [NSApp表示MAINMENU] ;
如果(!MAINMENU)
{
返回;
}
NSMenuItem * pluginsMenuItem = [MAINMENU itemWithTitle:@ “插件” ] ;
如果(!pluginsMenuItem)
{
pluginsMenuItem = [[NSMenuItem的alloc]初始化] ;
pluginsMenuItem .title伪 = @ “插件” ;
pluginsMenuItem .submenu = [[NSMenu页头] initWithTitle:pluginsMenuItem .title伪 ] ;
NSInteger的windowIndex = [MAINMENU indexOfItemWithTitle:@ “窗口” ] ;
[MAINMENU insertItem:pluginsMenuItem atIndex:windowIndex] ;
}
NSMenuItem * subMenuItem = [[NSMenuItem的alloc]初始化] ;
subMenuItem .title伪 = @ “FKConsole” ;
subMenuItem .TARGET =自我;
subMenuItem .action = @selector(toggleMenu :) ;
subMenuItem .STATE =值.boolValue NSOnState:NSOffState ;
[pluginsMenuItem .submenu的addItem:subMenuItem] ;
我们需要一个状态来表示插件的开关,刚好NSMenuItem
上有一个state
可以表示状态,而刚好显示效果也不错,我们就用它了。
图层
按钮添加完之后,我们现在需要获取到控制台的实例。很遗憾,苹果并没有给出文档。
很抱歉,我没有找到Mac软件开发上类似于Reveal的那种图层查看工具。喵神推荐了一个NSView
的Dumping类
,代码如下:
于来自http://onevcat.com/2013/02/xcode-plugin/。
- (无效)dumpWithIndent :( 的NSString *)缩进{
的NSString *类= NSStringFromClass([ 个体经营类]);
的NSString *信息= @ “” ;
如果([ 自 respondsToSelector:@selector(标题)]){
的NSString *标题= [ 自 performSelector:@selector(标题)];
如果(标题=!零 && [标题长度]> 0){
信息= [信息stringByAppendingFormat:@ “称号=%@” ,标题]
}
}
如果([ 自 respondsToSelector:@selector(stringValue的)]){
的NSString *字符串= [ 自 performSelector:@selector(stringValue的);
如果(字符串!= 零 && [字符串长度]> 0){
信息= [信息stringByAppendingFormat:@ “stringValue的=%@” ,字符串];
}
}
的NSString *提示= [ 自我工具提示];
如果(提示=!零 && [提示长度]> 0){
信息= [信息stringByAppendingFormat:@ “提示=%@” ,提示]。
}
的NSLog(@ “%@%@%@” ,缩进,类信息);
如果([ 自子视图]计数]> 0){
的NSString * subIndent = [ 的NSString stringWithFormat:@ “%@%@” ,缩进,([缩进长度] / 2)%2 == 0?@ “|”:@ “:” ];
为(*的NSView在[子视图自子视图]){
[子视图dumpWithIndent:subIndent];
}
}
}
效果类似于如下:
除了这种做法之外,我用的是chisel,这是facebook开源的一个LLDB的命令行辅助调试的工具。里面包含有一个pviews
命令,可以直接递归打印整个key窗口
,效果如下:
导入私有API
我们在里面找到了一个叫做IDEConsoleTextView
的类,这是在上图中看到的所有View中唯一包含Console
这个关键字的,我们查看一下它的frame,确定控制台就是它。
苹果并没有给将这个IDEConsoleTextView
放到AppKit
中,它是一个私有类,我们现在想要修改它,那么就需要拿到它的头文件。
Github上上有很多转储出来的Xcode中header,大家可以看一下:https://github.com/search?utf8=%E2%9C%93&q=xcode+header。我们在header中找到了IDEConsoleTextView.h
,处于IDEKit
中。
在头文件中可以看到,IDEConsoleTextView
是继承自DVTCompletingTextView
-> DVTTextView
->NSTextView
。NSTextView
中保存文字内容使用的是NSTextStorage *textStorage
,所以我们要修改的是IDEConsoleTextView
的textStorage
。但是我们在NSTextStorage
的头文件中并没有找到具体文字保存的属性,那我们这就去找。
功能开发
我们循环遍历所有的的NSView
,找到IDEConsoleTextView
,我们看一下它的信息:
我们没有找到它的textStorage
属性,我们尝试在控制台中打一下:
它是有这个属性的,只是在调试区没有看到。
textStorage
的代表中有两个方法,分别是:
// 发送 -Process内编辑固定属性之前。 代表们可以改变文字或属性。
- (无效) textStorage: (NSTextStorage *) textStorage willProcessEditing: (NSTextStorageEditActions) editedMask范围:(NSRange) editedRange changeInLength: (NSInteger的)增量NS_AVAILABLE (10 _11,7 _0) ;
//内发送正确-processEditing通知布局管理器前, 代表们可以改变属性。
- (无效) textStorage: (NSTextStorage *) textStorage didProcessEditing: (NSTextStorageEditActions) editedMask范围:(NSRange) editedRange changeInLength: (NSInteger的)增量NS_AVAILABLE (10 _11,7 _0) ;
textStorage
中字符或者描述被修改之后,会触发这个代理,那我们实现一下这个代理方法:
自.fkConsoleTextView .textStorage .delegate = 自我 ;
- (无效)textStorage:(NSTextStorage *)textStorage
willProcessEditing:(NSTextStorageEditActions)editedMask
范围:( NSRange)editedRange
changeInLength :( NSInteger的)三角洲
{
}
OK,这次我们找到了,IDEConsoleTextView
中有一个_contents
属性,这是一个继承自NSMutableAttributedString
的类,这个里面的mutableString
保存文字,mutableAttributes
保存对文字的描述。我们需要修改的就是这个mutableString
属性。
我们在代理方法中使用valueForKeyPath:
可以获取到mutableString
属性,那么,现在我们将它进行转换。
FKConsole
是用来调整控制台中文显示的,目的是将类似于这种的Unicode编码( \U6d4b\U8bd5"
)修改为( "测试啊"
)这种的正常显示。
我在计算器。上找到一种解决办法代码类似于这样:
于来自http://stackoverflow.com/questions/13240620/uilabel-text-with-unicode-nsstring
- (的NSString *)stringByReplaceUnicode :( 的NSString *)的字符串
{
的NSMutableString * convertedString = [字符串mutableCopy]
[convertedString replaceOccurrencesOfString:@ “\\ U” withString:@ “\\ U”选项:0范围:NSMakeRange(0,convertedString 。长度)];
CFStringRef变换= CFSTR( “ 任意六角/ Java”的);
CFStringTransform((__桥CFMutableStringRef)convertedString,NULL,变换,YES);
返回 convertedString;
}
我们使用的setValue:forKeyPath:
的方式去修改mutableString
属性。
运行,确实可以,但是有一些问题。
- 如果使用findView的方式去查找
IDEConsoleTextView
,然后去设置代理的话,那么,在什么时候去findView呢,如果这时候又新打开几个页面呢,这是不确定的。 - 修改后的文字长度和原先的不一样,哪怕修改了
editedRange
也没有用。这样的话,如果在控制台上输入文字或者调试命令,可能会崩溃,崩溃的主要原因是IDEConsoleTextView
用_startLocationOfLastLine
和_lastRemovableTextLocation
这两个属性去控制文字起始位置和删除位置,在设置mutableString
之后,由于长度不一,可能会发生字符串取值越界的问题,而NSTextStorage
的代理中又是获取不到持有它的IDEConsoleTextView
的。
监听通知
针对第一个问题,我们可以使用通知的方式去解决。
参照喵神的博客,可以监听全部的通知,然后去查找哪个是你所需要的。
- (ID)的init {
如果(自 = [ 超级初始化]){
[ NSNotificationCenter defaultCenter]的addObserver:自我
选择:@选择(的NotificationListener :)
名称:无目标:零 ]
}
回归 自我 ;
}
- (无效)的NotificationListener :( NSNotification *)的NotI {
的NSLog(@ “通知:%@”,[NotI位名]);
}
我们这里只需要监听NSTextDidChangeNotification
就行,然后在方法内去判断一下,之后再设置代理。
- (无效)textStorageDidChange:(NSNotification *)的NotI
{
如果([NotI位。对象 isKindOfClass:NSClassFromString(@“IDEConsoleTextView”)&&
((IDEConsoleTextView *)的NotI。对象).textStorage。委派!=个体经营)
{
(。(IDEConsoleTextView *)的NotI 对象)。.textStorage 委托 =自我;
}
}
这样就解决了第一个问题。
添加方法和手段的交叉混合
这里有兴趣的话,可以参考我另外一篇博客:Objective-C运行常见用法,里面以举例的方式讲解了常见的运行时用法。
针对第二个问题,我采用的办法是在适当的时候去修改IDEConsoleTextView
的_startLocationOfLastLine
和_lastRemovableTextLocation
属性。经实验,崩溃的方法主要是IDEConsoleTextView
的这些方法:
- (void) insertText: (id)arg1;
- (void) insertNewline: (id)arg1;
- (void)clearConsoleItems;
- ( BOOL ) shouldChangeTextInRanges: (id)arg1 replacementStrings: (id)arg2;
我给IDEConsoleTextView
在运行时添加了以下的方法:
- (void) fk_insertText: (id)arg1;
- (void) fk_insertNewline: (id)arg1;
- (void)fk_clearConsoleItems;
- ( BOOL ) fk_shouldChangeTextInRanges: (id)arg1 replacementStrings: (id)arg2;
之后,使用JRSwizzle来交换,混合方法,类似于这样:
- (无效) addMethodWithNewMethod: (SEL) newMethod originMethod: (SEL) originMethod
{
方法targetMethod = class_getInstanceMethod (NSClassFromString(@ “IDEConsoleTextView” ),newMethod);
方法consoleMethod = class_getInstanceMethod (self.class,新方法) ;
IMP consoleIMP = method_getImplementation (控制台方法) ;
如果(!目标的方法)
{
class_addMethod (NSClassFromString(@ “IDEConsoleTextView” ),newMethod,consoleIMP,method_getTypeEncoding (控制台方法));
如果(原产地法)
{
NSError *错误;
[NSClassFromString (@ “IDEConsoleTextView” )
jr_swizzleMethod:newMethod
withMethod:originMethod
错误:错误]
的NSLog (@ “错误=%@” ,错误) ;
}
}
}
在fk_
开头的系列方法中,添加了对IDEConsoleTextView
的检查:
- (无效) fk_checkTextView: (IDEConsoleTextView *)的TextView
{
如果(文本查看的.text 存储。长度<[文本查看值ForKeyPath:克StartLocationOfLastLineKey ]长的longValue ])
{
[TextView中的setValue:@ (文本查看的.text 存储。长度) forKeyPath:kStartLocationOfLastLineKey];
}
如果(文本查看的.text 存储。长度<[文本查看值ForKeyPath:克LastRemovableTextLocationKey ]长的longValue ])
{
[TextView中的setValue:@ (文本查看的.text 存储。长度) forKeyPath:kLastRemovableTextLocationKey];
}
}
- (无效) fk_insertText:(ID) ARG1
{
[个体经营fk_checkTextView: (IDEConsoleTextView *)个体经营];
[个体经营fk_insertText:ARG1];
}
这样,就解决了第二个问题。
OK,FKConsole
这就基本开发完成了。
恶魔岛
上文也提到了,Alcatraz
是一个开源的Xcode包管理器。事实上,Alcatraz
也成为了我们目前安装Xcode插件的最主要的工具。
现在我们将FKConsole
提交到恶魔
上。
填写
alcatraz-packages是Alcatraz
的包仓库列表,packages.json
保存了所有Alcatraz
支持的插件、色彩主题、模板。
我们fork一下alcatraz-packages到我们的代码仓库中。之后,仿照这种格式,添加上我们的项目。
{
“ 名 ”:“FKConsole” ,
“ url可 ”:“https://github.com/Forkong/FKConsole” ,
“ 说明 ”:“FKConsole是一个插件的Xcode调整控制台显示器(对中国)。” ,
“ 屏幕截图 ”:“https://raw.githubusercontent.com/Forkong/FKConsole/master/Screenshots/demo.gif”
}
respec
rspec
是用ruby写的一个测试框架,这里作者写了一个用于测试你修改过后的packages.json
是否合法的脚本。直接切到alcatraz-packages
目录下,运行rspec
命令即可。通过的话,会这样显示:
RSpec的
使用红宝石的宝石就能直接装上。
拉
校验没有问题之后,我们拉入请求
,我们的提交就出现在恶魔的程序包的拉请求
上了:
https://github.com/alcatraz/alcatraz-packages/pull/461
(大家千万不要像我一样,没看清除,直接添加到最后面了。它是有三个分类的,一定要看清楚,要添加到插件的分类上。)
Xcode7插件开发:从开发到拉到恶魔岛的更多相关文章
- IOS 开发下拉刷新和上拉加载更多
IOS 开发下拉刷新和上拉加载更多 简介 1.常用的下拉刷新的实现方式 (1)UIRefreshControl (2)EGOTTableViewrefresh (3)AH3DPullRefresh ( ...
- 快速解决js开发下拉框中blur与click冲突
在开发中我们会经常遇到blur和click冲突的情况.下面叙述了开发中常遇到的"下拉框"的问题,并提供了两种解决方案. 一.blur和click事件简述 blur事件:当元素失去焦 ...
- [IOS]从零开始搭建基于Xcode7的IOS开发环境和免开发者帐号真机调试运行第一个IOS程序HelloWorld
首先这篇文章比较长,若想了解Xcode7的免开发者帐号真机调试运行IOS程序的话,直接转到第五部分. 转载请注明原文地址:http://www.cnblogs.com/litou/p/4843772. ...
- zTree开发下拉树
最近,因为工作需要一个树形下拉框的组件,经过查资料一般有两种的实现方法.其一,就是使用zTree实现:其二,就是使用easyUI实现.因为公司的前端不是使用easyUI设计的,故这里我选择了zTree ...
- portal开发"下拉框"“日期框”查询要怎么配置
下面的这些是我今天的成果! 总的来说是一步一步摸索出来的!还是等感谢超哥的耐心指导,犯了一些错误! 1.比如在wd配置文件中中写id=“check_it_two”,在java中写成 checki_it ...
- Android开发 - 下拉刷新和分段头悬停列表
项目源码 本文所述项目已开源,源码地址 为什么做PullToRefresh-PinnedSection-ListView 前段时间因为项目需求,需要在Android中对ListView同时增加下拉刷新 ...
- Android 开发 上拉加载更多功能实现
实现思维 开始之前先废话几句,Android系统没有提供上拉加载的控件,只提供了下拉刷新的SwipeRefreshLayout控件.这个控件我们就不废话,无法实现上拉刷新的功能.现在我们说说上拉加载更 ...
- sonarqube插件开发(二) 开发插件
一.环境准备 java 1.8, maven 3.1 检查自己的环境是否支持 sonarqube的插件开发 java -version mvn -version 二.创建maven项目 pom.xml ...
- 微信小程序开发之下拉菜单
实现功能:点击维保人员,调出下拉菜单.选择子菜单时,显示右边的图标表示选中,并进行赋值并进行搜索筛选 Wxml: <view class="dtclass" bindtap= ...
随机推荐
- wpf 依赖性属性
1 依赖性属性的作用 在WPF体系中,只有定义属性为依赖项属性,这个属性才支持样式设置,数据绑定,继承,动画和默认值.也就是 这个属性才能具有WPF中的一些特点. 它支持自动通知UI控件. WPF的属 ...
- Rectangle and Square(判断正方形、矩形)
http://acm.sdut.edu.cn:8080/vjudge/contest/view.action?cid=42#problem/D 改了N多次之后终于A了,一直在改判断正方形和矩形那,判断 ...
- 【转】VC的MFC中重绘函数的使用总结(整理)
原文网址:http://www.cnblogs.com/x8023z/archive/2008/12/09/mfc33.html 在刷新窗口时经常要调用重绘函数MFC提供了三个函数用于窗口重绘Inva ...
- HDU 5924 Mr. Frog’s Problem 【模拟】 (2016CCPC东北地区大学生程序设计竞赛)
Mr. Frog's Problem Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Other ...
- 实例讲解MSSQL日期相加减比大小函数 转
1. 当前系统日期.时间select getdate() 2. dateadd 在向指定日期加上一段时间的基础上,返回新的 datetime 值例如:向日期加上2天select dateadd(day ...
- Selenium 处理模态对话框
模态对话框的原理 模态窗口 点击下一步的动作为,聚焦到“下一步”,然后直接回车 driver.FindElement(By.CssSelector("div.rg_btn a")) ...
- 关于将客户端移植到Lua的解决方案设想。
现在发行商都需要cp们做热更新,而对于unity制作的游戏来讲,这个恐怕是个噩梦,而项目已经进行到中后期,确实很麻烦,有UniLua,但是如果全部手动解决恐怕上不了线了工作量太大,初步设想如果做一个基 ...
- 在LINUX终端和VIM下复制粘贴
http://www.tinylab.org/linux-terminal-and-paste-copy-under-vim/ 在GUI界面下,我们可以很自由的复制粘贴.但是在字符界面下,我们不得不用 ...
- 定制Centos系统(基于6.x)
1.条件准备: 按照需求,最小化安装Centos原生系统 在安装后的系统中找到/root/install.log与/root/anaconda-ks.cfg文件 ...
- php中strlen和{}的效率对比
很少有人知道{}用来判断字符串长度 今天试试 发现好像没有strlen快