出发点
今天工作中写了一个工具类,在.m中完成所有功能后,发觉把所有接口从.m中拷贝到.h中声明,好麻烦啊,所以就考虑写个命令行工具来做这些工作。
想要达到的结果
我们设计这个小工具,在终端中直接运行,传入一个.m文件路径参数,输出其中所有的方法名。
input:
> fti PWFileController.m
output:
- (NSString *)bytesToAvaiUnit:(long long)bytes;
- (long long) fileSizeAtPath:(NSString*) filePath;
- (long long) folderSizeAtPath:(NSString*) folderPath;
- (void) clearFolderAtPath:(NSString*) folderPath;
- (float)getTotalDiskSpace;
- (NSString *)getHomeDirectory;
开始
第一步新建一个mac的命令行(Command Line Tool)项目,这种项目只有一个main.m
文件,内容如下
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
这里先分析一下原理,首先.m文件中的C函数方法是不带自动内存池的,所以要在C方法中使用ObjC代码,必须使用@autoreleasepool
大括号括起来,这样才能保证在C方法结束后,栈内存能够释放。
其次,main函数中的argc参数,代表命令行中参数的个数,argv这个char数组,是每个参数的内容。
所以我们首先判断argc的个数,这里要注意,shell中的命令本身占一个参数位,所以没有任何参数的时候,argc应该为1。
if(argc<=1) return 0; //当argc<=1直接退出程序
接着我们要获取命令行输入的第二个参数,也就是.m文件路径
NSString* filePath = [[NSString alloc] initWithCString:argv[1] encoding:NSUTF8StringEncoding];
如果文件不存在,则结束程序
if(![[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
NSLog(@"文件不存在");
return 0;
}
接着我们在main函数之前声明一个找接口的方法,这个方法要用C语言方法的格式声明
NSArray* findInterface (NSString* text);
然后实现它,注意要加@autoreleasepool
NSArray* findInterface (NSString* text)
{
@autoreleasepool {
NSString *regex = @"-\\s?\\(.*?\\).*?(?=\\n|$|\\{)";
NSString *str = text;
NSError *error;
NSRegularExpression *regular = [NSRegularExpression regularExpressionWithPattern:regex
options:NSRegularExpressionCaseInsensitive
error:&error];
// 对str字符串进行匹配
NSArray *matches = [regular matchesInString:str
options:0
range:NSMakeRange(0, str.length)];
NSMutableArray* result = [NSMutableArray arrayWithCapacity:matches.count];
// 遍历匹配后的每一条记录
for (NSTextCheckingResult *match in matches) {
NSRange range = [match range];
NSString *mStr = [str substringWithRange:range];
[result addObject:mStr];
}
return [result copy];
}
}
这一段正则表达式的搜索没有特别要说明的,关于NSRegularExpression
这个类的正则的用法,比较简单,参考上面代码就行,所以我简单说下正则的匹配规则
-\\s?\\(.*?\\).*?(?=\\n|$|\\{)
以-
符号开头,在第一个左括号中间有若干空格,然后有若干空格和字符,然后有一个右括号,接下来又是若干个空格和字符,结尾要匹配三个,换行符\n
,字符串结尾$
和左大括号{
这样我们在main方法中读取文件内容,然后调用这个方法即可输出所有的接口名。
NSString* s = [[NSString alloc] initWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
NSString* f = [[findInterface(s) componentsJoinedByString:@";\n"] stringByAppendingString:@";"];
NSLog(@"result:\n%@",f);
完整的代码请参考 项目github
使用
这个项目通过菜单 Product -> Archive 可以发布released版本的运行程序,然后将其拷贝到/usr/local/bin
目录下,即可在terminal中直接使用。
注意我为了方便,把Archive出来的运行程序名,简化为fti
。
补充
类方法匹配,把正则中的-
改为(-|\\+)
即可。
换行的方法,可以根据{
来匹配,把(?=\\n|$|\\{)
改为[^;]*?(?=\\{)
。
原理各位自己分析。
因为修改了匹配规则,我们需要对抓取的内容进行一些处理,
在findInterface方法中,我们去掉检索内容的换行符和;
,用stringByReplacingOccurrencesOfString
方法实现
for (NSTextCheckingResult *match in matches) {
NSRange range = [match range];
NSString *mStr = [str substringWithRange:range];
mStr = [mStr stringByReplacingOccurrencesOfString:@"\n" withString:@""];
mStr = [mStr stringByReplacingOccurrencesOfString:@";" withString:@""];
mStr = format(mStr); //这个方法在下面的内容zh
[result addObject:mStr];
}
然后我们增加一个format方法,来把多行函数,格式化成标准的一行函数。
NSString* format (NSString* string){
@autoreleasepool {
string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSArray *components = [string componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
components = [components filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"self <> ''"]];
string = [components componentsJoinedByString:@" "];
return string;
}
}