出發點
今天工作中寫了一個工具類,在.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|$|\{)
以-
符號開頭,在第一個左括號中間有若干空格,然後有若干空格和字元,然後有一個右括號,接下來又是若干個空格和字元,結尾要匹配三個,換行符
,字串結尾
$
和左大括號{
這樣我們在main方法中讀取檔案內容,然後呼叫這個方法即可輸出所有的介面名。
NSString* s = [[NSString alloc] initWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
NSString* f = [[findInterface(s) componentsJoinedByString:@";
"] stringByAppendingString:@";"];
NSLog(@"result:
%@",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:@"
" 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;
}
}