Xcode插件开发
嘿嘿,今天带大家学习一下基于Xcode的插件开发。可能很多人一听到插件开发,想到的都是Sublime Text,Atom这样轻量级的编辑器的扩展插件,但是实际上,无论是VisualStudio, Eclipse以及Xcode这样重量级的IDE,都是支持自定义的插件开发的。学习好了Xcode的插件开发,不仅可以打造度身定做的神器,也有助于你将来进行Mac OS的应用开发。
DXXcodeConsoleUnicodePlugin
DXXcodeConsoleUnicodePlugin是一个帮助你自动将\u6061
这样的unicode码转换成对应的汉字的插件。
这个有什么用呢?想想看,我们在网络传输的时候,服务器如果返回的数据是中文(或者非ASCII码),通过NSLog在console输出的内容是不直观的,基本都是类似\u6061
这种,这对于我们开发调试来说是非常困难的。
因此,这款插件可以自动帮助我们将检测到的Unicode字符进行转换,直接输出成我们想要的对应内容。怎么样?让我们赶快来一探究竟吧!
在开始探讨实现之前,我个人首先强调一点,基于Unicode检测对应的字符是一个非常难的问题。不仅仅是中文,韩文、日文、big-5字符等等都属于Unicode,这些字符集之间好常常有交集。现有比较好的开源实现是Mozilla的UcharSet**。
实现
首先打开工程,文件结构如下:
- DXXcodeConsoleUnicodePlugin.h/.m
- RegExCategories.h/.m
其中,DXXcodeConsoleUnicodePlugin
是入口。同传统的iOS/Mac OS开发不同,插件开发并不存在传统意义上的main函数,更多的是利用所谓的Template Method
设计模式将你需要的自定义部分进行复写。
于是,我们可以看到如下三段函数:
+ (void)pluginDidLoad:(NSBundle *)plugin
{
static dispatch_once_t onceToken;
NSString *currentApplicationName = [[NSBundle mainBundle] infoDictionary][@"CFBundleName"];
if ([currentApplicationName isEqual:@"Xcode"]) {
dispatch_once(&onceToken, ^{
sharedPlugin = [[self alloc] initWithBundle:plugin];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(menuDidChange)
name:NSMenuDidChangeItemNotification
object:nil];
});
}
}
- (id)initWithBundle:(NSBundle *)plugin
{
if (self = [super init]) {
// reference to plugin's bundle, for resource acccess
self.bundle = plugin;
// Create menu items, initialize UI, etc.
// Sample Menu Item:
[self createMenu];
IMP_IDEConsoleItem_initWithAdaptorType = ReplaceInstanceMethod(NSClassFromString(@"IDEConsoleItem"), @selector(initWithAdaptorType:content:kind:),
[XcodeConsoleUnicode_IDEConsoleItem class], @selector(initWithAdaptorType:content:kind:));
}
return self;
}
- (void)createMenu
{
NSMenuItem *menuItem = [[NSApp mainMenu] itemWithTitle:@"Edit"];
if (menuItem && !self.convertInConsoleItem) {
[[menuItem submenu] addItem:[NSMenuItem separatorItem]];
NSMenuItem *convertItem = [[NSMenuItem alloc] initWithTitle:@"ConvertUnicode" action:@selector(convertAction) keyEquivalent:@"c"];
[convertItem setKeyEquivalentModifierMask:NSAlternateKeyMask];
[convertItem setTarget:self];
[[menuItem submenu] addItem:convertItem];
self.convertInConsoleItem = [[NSMenuItem alloc] initWithTitle:@"ConvertUnicodeInConsole"
action:@selector(convertUnicodeInConsoleAction)
keyEquivalent:@""];
[self.convertInConsoleItem setTarget:self];
[[menuItem submenu] addItem:self.convertInConsoleItem];
sIsConvertInConsoleEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:sConvertInConsoleEnableKey];
if (sIsConvertInConsoleEnabled) {
self.convertInConsoleItem.state = NSOnState;
} else {
self.convertInConsoleItem.state = NSOffState;
}
}
}
上面三段函数我们一一进行解析。
pluginDidLoad
,大家可以理解为插件的程序入口,在这个入口中我们通过单例进行我们自己开发的插件加载。**之所以使用单例是因为这个pluginDidLoad
可能会由于加载多个插件而被多次触发。initWithBundle
函数是我们自定义插件的构造函数,我们通过它进行自己任务的创建和调用。createMenu
则是对Xcode编辑器上的菜单添加属于我们自己的选项。
在这里,作者在Edit菜单下创建了属于自己的ConvertUnicode以及ConvertUnicodeInConsole,并对这些选项进行了快捷键绑定。
这些东西,除了自定义的菜单项及操作需要我们自己写以外,我们都可以通过Plugin Template
这个插件自动生成。
到现在,我们还没有看到任何实质性的转换内容,别急,在initWithBundle
中,作者通过Method Swizzling将IDEConsoleItem
的- (id)initWithAdaptorType:(id)arg1 content:(id)arg2 kind:(int)arg3
和自己实现的XcodeConsoleUnicode_IDEConsoleItem
进行的调换。
然后在替换后的方法中,实现解析,代码如下:
- (id)initWithAdaptorType:(id)arg1 content:(id)arg2 kind:(int)arg3
{
id item = IMP_IDEConsoleItem_initWithAdaptorType(self, _cmd, arg1, arg2, arg3);
if (sIsConvertInConsoleEnabled) {
NSString *logText = [item valueForKey:@"content"];
NSString *resultText = [DXXcodeConsoleUnicodePlugin convertUnicode:logText];
[item setValue:resultText forKey:@"content"];
}
return item;
}
这个方法非常简单,通过原方法获取console中的item,并获取对应的content进行解析。而解析也仅仅是采用了UTF8StringEncoding
直接进行转换。
补充知识:NSRegularExpression和正则表达式
在本文的实现当中,作者对于中文字符的Unicode的表达方式\u4582这样的格式,采用了正则表达式进行了提取。在传统的Unicode的格式中,单独一个\
表示为转义字符,不能直接表达一般字符。所以,在正则表达式中,我们需要采用\\
来表示一个\
。同时,对于4582这样的字符,我们当然可以认为其模式为四个连续的字符,所以我们可以采用\w{4}
。(切记,不能采用\W
。大写的\W
表征的是非字符。)然后{4}
表示前面的模式重复4次,即\w
连续出现4次。
好了,综上所述,我们不难写出针对中文Unicode提取的正则表达式:\\u\w{4}
但是,在作者的代码中,作者的正则表达式却是:\\\\[uU]\\w{4}
,那这个是怎么回事呢?
原因在于, 对于在字符串形式出现的正则表达式,首先解析的是字符串规则,然后才是正则表达式引擎的解析。
所以,\\\\
被字符串解析成\\
,然后正则解析成\
。然后对于[uU]
,是一个组,表示或者u或者U,因为有些输出的文本里,对于U的大小写并没有规定,所以两种情况都需要考虑。
后面的就不再赘述了,原理一致。大家有兴趣的自己深入学习下吧。