教你写一个可以找到.m文件所有接口名的命令行工具

项目github

出发点

今天工作中写了一个工具类,在.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;
    }
}

代码与哲学
独立游戏开发者

独立游戏开发者

2.1k 声望
81 粉丝
0 条评论
推荐阅读
interface和setter,getter
ObjC的@interface设计,跟Java和C#真的很像,但又略有不同,相比之下Java和C#则像是一个模子刻出来的。ObjC的特点十分明显,首先是一般不用写@private和@public来区分私有变量,大部分ObjC开发者甚至都不知道还有...

秋刀生鱼片阅读 1.8k

iOS 健康共享失败如何解决
您要开始与之共享的对象必须已经连同他们的 iCloud 账户邮箱一起保存在您的“通讯录”中(iCloud 账户邮箱即 iCloud 账户绑定的邮箱信息,不是强制要求 @iCloud.com 邮箱)。

岚哲阅读 5k

探究 iOS 内存问题
本文从 Tagged Pointer、objc 源码、dealloc 原理、AutoreleasePool 原理、野指针探究等技术点展开聊了聊 iOS 内存相关问题。

杭城小刘1阅读 1.4k

封面图
iOS IDA逆向之patch
这里介绍的是ida的patch.1.搜索svc #0x80,回到IDA View-A界面,才能正确搜索点击图中T字按钮,弹出搜索框点击列表中进入2.修改svc #0x80,在IDA View-A界面中选中svc那一行,点击工具栏Edit--&gt;Patch program--&gt...

宋冬野阅读 2k

社交场景下iOS消息流交互层实践
一款社交产品的诞生,离不开即时通讯(IM)场景。随着团队业务版图在社交领域的布局,诞生了多个社交场景APP,涉及的IM场景,包含私聊、群聊、聊天室等。

云音乐技术团队1阅读 434

封面图
SegmentFault 思否技术周刊 Vol.77 — 探究关于 iOS 的特性
本文从 Tagged Pointer、objc 源码、dealloc 原理、AutoreleasePool 原理、野指针探究等技术点展开聊了聊 iOS 内存相关问题。

Beverly阅读 1.4k

封面图
魔方带壳截图:让你的截图看起来更加高大上
我们在日常工作和生活中,往往需要截图去做汇报或分享。普通人的做法:手机或电脑截图,然后就拿去汇报或分享。这样你的截图显得平平无奇,没有给人眼前一亮的感觉。但是,如果我们给这张截图穿上一件“衣服”,把...

老人羽海阅读 1.2k

封面图

独立游戏开发者

2.1k 声望
81 粉丝
宣传栏