教你寫一個可以找到.m檔案所有介面名的命令列工具

秋刀生魚片發表於2017-05-12

專案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|$|\{)

-符號開頭,在第一個左括號中間有若干空格,然後有若干空格和字元,然後有一個右括號,接下來又是若干個空格和字元,結尾要匹配三個,換行符
,字串結尾$和左大括號{

這樣我們在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;
    }
}

相關文章