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中,有些会添加到ViewWindow中,我找半天都没找到选项在哪,还不如直接建一个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的那种图层查看工具。喵神推荐了一个NSViewDumping类,代码如下:

于来自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 ->NSTextViewNSTextView中保存文字内容使用的是NSTextStorage *textStorage,所以我们要修改的是IDEConsoleTextViewtextStorage。但是我们在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属性。

运行,确实可以,但是有一些问题。

  1. 如果使用findView的方式去查找IDEConsoleTextView,然后去设置代理的话,那么,在什么时候去findView呢,如果这时候又新打开几个页面呢,这是不确定的。
  2. 修改后的文字长度和原先的不一样,哪怕修改了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-packagesAlcatraz的包仓库列表,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插件开发:从开发到拉到恶魔岛的更多相关文章

  1. IOS 开发下拉刷新和上拉加载更多

    IOS 开发下拉刷新和上拉加载更多 简介 1.常用的下拉刷新的实现方式 (1)UIRefreshControl (2)EGOTTableViewrefresh (3)AH3DPullRefresh ( ...

  2. 快速解决js开发下拉框中blur与click冲突

    在开发中我们会经常遇到blur和click冲突的情况.下面叙述了开发中常遇到的"下拉框"的问题,并提供了两种解决方案. 一.blur和click事件简述 blur事件:当元素失去焦 ...

  3. [IOS]从零开始搭建基于Xcode7的IOS开发环境和免开发者帐号真机调试运行第一个IOS程序HelloWorld

    首先这篇文章比较长,若想了解Xcode7的免开发者帐号真机调试运行IOS程序的话,直接转到第五部分. 转载请注明原文地址:http://www.cnblogs.com/litou/p/4843772. ...

  4. zTree开发下拉树

    最近,因为工作需要一个树形下拉框的组件,经过查资料一般有两种的实现方法.其一,就是使用zTree实现:其二,就是使用easyUI实现.因为公司的前端不是使用easyUI设计的,故这里我选择了zTree ...

  5. portal开发"下拉框"“日期框”查询要怎么配置

    下面的这些是我今天的成果! 总的来说是一步一步摸索出来的!还是等感谢超哥的耐心指导,犯了一些错误! 1.比如在wd配置文件中中写id=“check_it_two”,在java中写成 checki_it ...

  6. Android开发 - 下拉刷新和分段头悬停列表

    项目源码 本文所述项目已开源,源码地址 为什么做PullToRefresh-PinnedSection-ListView 前段时间因为项目需求,需要在Android中对ListView同时增加下拉刷新 ...

  7. Android 开发 上拉加载更多功能实现

    实现思维 开始之前先废话几句,Android系统没有提供上拉加载的控件,只提供了下拉刷新的SwipeRefreshLayout控件.这个控件我们就不废话,无法实现上拉刷新的功能.现在我们说说上拉加载更 ...

  8. sonarqube插件开发(二) 开发插件

    一.环境准备 java 1.8, maven 3.1 检查自己的环境是否支持 sonarqube的插件开发 java -version mvn -version 二.创建maven项目 pom.xml ...

  9. 微信小程序开发之下拉菜单

    实现功能:点击维保人员,调出下拉菜单.选择子菜单时,显示右边的图标表示选中,并进行赋值并进行搜索筛选 Wxml: <view class="dtclass" bindtap= ...

随机推荐

  1. Git入门简介

    ​1. Git 背景 Git 最初由Linus Torvalds编写,用于 Linux 内核开发的版本控制工具. Git 与常用的版本控制工具 CVS.Subversion 等不同,它采用了分布式版本 ...

  2. 14.8.3 Physical Row Structure of InnoDB Tables InnoDB 表的物理行结构

    14.8.3 Physical Row Structure of InnoDB Tables InnoDB 表的物理行结构 一个InnoDB 表的物理行结构取决于在创建表指定的行格式 默认, Inno ...

  3. 【HDOJ】1150 Machine Schedule

    匈牙利算法. #include <stdio.h> #include <string.h> #define MAXNUM 1005 char map[MAXNUM][MAXNU ...

  4. tomcat 配置内存相关

    今天早上 ,tomcat 网站页面上出现报错问题.最后还是一位同事解决的,这里记录一下. 1.看了一下页面,他说是内存溢出. 首先找到 双击 Tomw.exe 出现如下图 然后需要配置堆栈大小

  5. HTML---网页编程(1)

    前 言 HTML需要和CSS还有JS一起用,才能提现强大. 所以,学了HTML.最好去学学CSS还有JS(JavaScript) ☆静态页面和动态页面 网站页面分为静态页面和动态页面两种 • 静态页面 ...

  6. js 中 json对象 与 json字符串 间相互转换

    在数据传输过程中,json是以文本,即字符串的形式传递的,而JS操作的是JSON对象,所以,JSON对象和JSON字符串之间的相互转换是关键 JSON字符串:  var str1 = '{ " ...

  7. VS2010 MFC GDI+ 实现PNG透明图片显示

    网上找了一些资料学习了一下PNG图的显示,这里总结一下. 参考:http://blog.csdn.net/czyt1988/article/details/7965066 一.VS2010配置GDI+ ...

  8. JavaScript高级程序设计3.pdf

    do-while语句是一种后测试循环语句do {statement} while (expression),至少会循环一次 while语句是一种前测试循环语句while (expression) st ...

  9. Bzoj 1222: [HNOI2001]产品加工 动态规划

    1222: [HNOI2001]产品加工 Time Limit: 15 Sec  Memory Limit: 162 MBSubmit: 486  Solved: 298[Submit][Status ...

  10. linux创建用户,指定组

    本博客不再更新 该文章新链接移步:http://it.lovepet.vip/archives/7/ 一.创建用户: 1.使用命令 useradd 例:useradd test——创建用户test  ...