App瘦身

寫(xiě)文章注冊(cè)登錄

×

使用Swift3開(kāi)發(fā)了個(gè)MacOS的程序可以檢測(cè)出objc項(xiàng)目中無(wú)用方法,然后一鍵全部清理

?

星光社的戴銘?

?關(guān)注

2016.10.28 21:27*?字?jǐn)?shù) 1901?閱讀 1633評(píng)論 8喜歡 29

當(dāng)項(xiàng)目越來(lái)越大,引入第三方庫(kù)越來(lái)越多,上架的 APP 體積也會(huì)越來(lái)越大,對(duì)于用戶來(lái)說(shuō)體驗(yàn)必定是不好的。在清理資源,編譯選項(xiàng)優(yōu)化,清理無(wú)用類(lèi)等完成后,能夠做而且效果會(huì)比較明顯的就只有清理無(wú)用函數(shù)了。

一種方案是我們滴滴的王康基于clang插件這樣一個(gè)源碼級(jí)別的分析工具來(lái)分析代碼間的調(diào)用關(guān)系達(dá)到分析出無(wú)用代碼的目的,文章在這里:?基于clang插件的一種iOS包大小瘦身方案?文章里對(duì)objc方法的定義,調(diào)用,實(shí)現(xiàn)的全面說(shuō)明達(dá)到了極致,非常值得一看。

另一種方案是根據(jù)?Linkmap?文件取到objc的所有類(lèi)方法和實(shí)例方法。再用工具比如 otool 命令逆向出可執(zhí)行文件里引用到的方法名然后通過(guò)求差集得到無(wú)用函數(shù),由于API的回調(diào)也會(huì)被認(rèn)為是無(wú)用函數(shù),所以這個(gè)方案還需要將這些回調(diào)函數(shù)加到白名單里過(guò)濾。具體說(shuō)明,可以看看微信團(tuán)隊(duì)的這篇文章:?iOS微信安裝包瘦身

還有一種使用了 *?machoview?* 從?Mach-O?里獲取信息進(jìn)行無(wú)用方法和文件的處理。阿里有篇文章對(duì) Mach-O 的處理做了詳細(xì)的說(shuō)明:?減小ipa體積之刪除frameWork中無(wú)用mach-O文件

這幾個(gè)現(xiàn)有方案有些比較麻煩的地方,因?yàn)闄z索出的無(wú)用方法沒(méi)法確定能夠直接刪除,還需要挨個(gè)檢索人工判斷是否可以刪除,這樣每次要清理時(shí)都需要這樣人工排查一遍是非常耗時(shí)耗力的。

這樣就只有模擬編譯過(guò)程對(duì)代碼進(jìn)行深入分析才能夠找出確定能夠刪除的方法。具體效果可以先試試看,程序代碼在:https://github.com/ming1016/SMCheckProject?選擇工程目錄后程序就開(kāi)始檢索無(wú)用方法然后將其注釋掉。

設(shè)置結(jié)構(gòu)體 ??

首先確定結(jié)構(gòu),類(lèi)似先把?OC?文件根據(jù)語(yǔ)法畫(huà)出整體結(jié)構(gòu)。先看看?OC Runtime?里是如何設(shè)計(jì)的結(jié)構(gòu)體。

structobjc_object{Class isa? OBJC_ISA_AVAILABILITY;};/*類(lèi)*/structobjc_class{Class isa? OBJC_ISA_AVAILABILITY;#if!__OBJC2__Class super_class;constchar*name;longversion;longinfo;longinstance_size;structobjc_ivar_list*ivars;structobjc_method_list**methodLists;structobjc_cache*cache;structobjc_protocol_list*protocols;#endif};/*成員變量列表*/structobjc_ivar_list{intivar_count#ifdef__LP64__intspace#endif/* variable length structure */structobjc_ivarivar_list[1]}? ? ? /*成員變量結(jié)構(gòu)體*/structobjc_ivar{char*ivar_namechar*ivar_typeintivar_offset#ifdef__LP64__intspace#endif}/*方法列表*/structobjc_method_list{structobjc_method_list*obsolete;intmethod_count;#ifdef__LP64__intspace;#endif/* variable length structure */structobjc_methodmethod_list[1];};/*方法結(jié)構(gòu)體*/structobjc_method{SEL method_name;char*method_types;/* a string representing argument/return types */IMP method_imp;};

一個(gè) class 只有少量函數(shù)會(huì)被調(diào)用,為了減少較大的遍歷所以創(chuàng)建一個(gè)?objc_cache?,在找到一個(gè)方法后將?method_name?作為 key,將?method_imp?做值,再次發(fā)起時(shí)就可以直接在 cache 里找。

使用 swift 創(chuàng)建類(lèi)似的結(jié)構(gòu)體,做些修改

//文件classFile:NSObject{//文件publicvartype =FileType.FileHpublicvarname =""publicvarcontent =""publicvarmethods = [Method]()//所有方法publicvarimports = [Import]()//引入類(lèi)}//引入structImport{publicvarfileName =""}//對(duì)象classObject{publicvarname =""publicvarsuperObject =""publicvarproperties = [Property]()publicvarmethods = [Method]()}//成員變量structProperty{publicvarname =""publicvartype =""}structMethod{publicvarclassMethodTf =false//+ or -publicvarreturnType =""publicvarreturnTypePointTf =falsepublicvarreturnTypeBlockTf =falsepublicvarparams = [MethodParam]()publicvarusedMethod = [Method]()publicvarfilePath =""http://定義方法的文件路徑,方便修改文件使用publicvarpnameId =""http://唯一標(biāo)識(shí),便于快速比較}classMethodParam:NSObject{publicvarname =""publicvartype =""publicvartypePointTf =falsepublicvariName =""}classType:NSObject{//todo:更多類(lèi)型publicvarname =""publicvartype =0//0是值類(lèi)型 1是指針}```swift## 開(kāi)始語(yǔ)法解析 ??首先遍歷目錄下所有的文件。```swiftletfileFolderPath =self.selectFolder()letfileFolderStringPath = fileFolderPath.replacingOccurrences(of:"file://", with:"")letfileManager =FileManager.default;//深度遍歷letenumeratorAtPath = fileManager.enumerator(atPath: fileFolderStringPath)//過(guò)濾文件后綴letfilterPath =NSArray(array: (enumeratorAtPath?.allObjects)!).pathsMatchingExtensions(["h","m"])

然后將注釋排除在分析之外,這樣做能夠有效避免無(wú)用的解析。

分析是否需要按照行來(lái)切割,在?@interface?,?@end?和?@ implementation?,?@end里面不需要換行,按照;符號(hào),外部需要按行來(lái)。所以兩種切割都需要。

先定義語(yǔ)法標(biāo)識(shí)符

classSb:NSObject{publicstaticletadd ="+"publicstaticletminus ="-"publicstaticletrBktL ="("publicstaticletrBktR =")"publicstaticletasterisk ="*"publicstaticletcolon =":"publicstaticletsemicolon =";"publicstaticletdivide ="/"publicstaticletagBktL ="<"publicstaticletagBktR =">"publicstaticletquotM ="\""publicstaticletpSign ="#"publicstaticletbraceL ="{"publicstaticletbraceR ="}"publicstaticletbktL ="["publicstaticletbktR ="]"publicstaticletqM ="?"publicstaticletupArrow ="^"publicstaticletinteface ="@interface"publicstaticletimplementation ="@implementation"publicstaticletend ="@end"publicstaticletselector ="@selector"publicstaticletspace =" "publicstaticletnewLine ="\n"}

接下來(lái)就要開(kāi)始根據(jù)標(biāo)記符號(hào)來(lái)進(jìn)行切割分組了,使用?Scanner?,具體方式如下

//根據(jù)代碼文件解析出一個(gè)根據(jù)標(biāo)記符切分的數(shù)組classfunccreateOCTokens(conent:String) -> [String]{varstr = conent? ? str =self.dislodgeAnnotaion(content: str)//開(kāi)始掃描切割letscanner =Scanner(string: str)vartokens = [String]()//Todo:待處理符號(hào),.letoperaters = [Sb.add,Sb.minus,Sb.rBktL,Sb.rBktR,Sb.asterisk,Sb.colon,Sb.semicolon,Sb.divide,Sb.agBktL,Sb.agBktR,Sb.quotM,Sb.pSign,Sb.braceL,Sb.braceR,Sb.bktL,Sb.bktR,Sb.qM]varoperatersString =""foropinoperaters {? ? ? ? operatersString = operatersString.appending(op)? ? }varset=CharacterSet()set.insert(charactersIn: operatersString)set.formUnion(CharacterSet.whitespacesAndNewlines)while!scanner.isAtEnd {foroperaterinoperaters {if(scanner.scanString(operater, into:nil)) {? ? ? ? ? ? ? ? tokens.append(operater)? ? ? ? ? ? }? ? ? ? }varresult:NSString?? ? ? ? result =nil;ifscanner.scanUpToCharacters(from:set, into: &result) {? ? ? ? ? ? tokens.append(resultas!String)? ? ? ? }? ? }? ? tokens = tokens.filter{? ? ? ? $0!=Sb.space? ? }returntokens;}

行解析的方法

//根據(jù)代碼文件解析出一個(gè)根據(jù)行切分的數(shù)組classfunccreateOCLines(content:String) -> [String]{varstr = content? ? str =self.dislodgeAnnotaion(content: str)letstrArr = str.components(separatedBy:CharacterSet.newlines)returnstrArr}

根據(jù)結(jié)構(gòu)將定義的方法取出 ??

- (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(NSString*)path cacheTime:(NSInteger)cacheTime subDirectory:(NSString*)subDirectory;

這里按照語(yǔ)法規(guī)則順序取出即可,將方法名,返回類(lèi)型,參數(shù)名,參數(shù)類(lèi)型記錄。這里需要注意?Block?類(lèi)型的參數(shù)

- (STMPartMaker *(^)(STMPartColorType))colorTypeIs;

這種類(lèi)型中還帶有括號(hào)的語(yǔ)法的解析,這里用到的方法是對(duì)括號(hào)進(jìn)行計(jì)數(shù),左括號(hào)加一右括號(hào)減一的方式取得完整方法。

獲得這些數(shù)據(jù)后就可以開(kāi)始檢索定義的方法了。我寫(xiě)了一個(gè)類(lèi)專(zhuān)門(mén)用來(lái)獲得所有定義的方法

classfuncparsingWithArray(arr:Array) ->Method{varmtd =Method()varreturnTypeTf =false//是否取得返回類(lèi)型varparsingTf =false//解析中varbracketCount =0//括弧計(jì)數(shù)varstep =0//1獲取參數(shù)名,2獲取參數(shù)類(lèi)型,3獲取iNamevartypes = [String]()varmethodParam =MethodParam()//print("\(arr)")forvartkinarr {? ? ? ? tk = tk.replacingOccurrences(of:Sb.newLine, with:"")if(tk ==Sb.semicolon || tk ==Sb.braceL) && step !=1{varshouldAdd =falseifmtd.params.count>1{//處理這種- (void)initWithC:(type)m m2:(type2)i, ... NS_REQUIRES_NIL_TERMINATION;入?yún)槎鄥?shù)情況ifmethodParam.type.characters.count>0{? ? ? ? ? ? ? ? ? ? shouldAdd =true}? ? ? ? ? ? }else{? ? ? ? ? ? ? ? shouldAdd =true}ifshouldAdd {? ? ? ? ? ? ? ? mtd.params.append(methodParam)? ? ? ? ? ? ? ? mtd.pnameId = mtd.pnameId.appending("\(methodParam.name):")? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? }elseiftk ==Sb.rBktL {? ? ? ? ? ? bracketCount +=1parsingTf =true}elseiftk ==Sb.rBktR {? ? ? ? ? ? bracketCount -=1ifbracketCount ==0{vartypeString =""fortypeTkintypes {? ? ? ? ? ? ? ? ? ? typeString = typeString.appending(typeTk)? ? ? ? ? ? ? ? }if!returnTypeTf {//完成獲取返回mtd.returnType = typeString? ? ? ? ? ? ? ? ? ? step =1returnTypeTf =true}else{ifstep ==2{? ? ? ? ? ? ? ? ? ? ? ? methodParam.type = typeString? ? ? ? ? ? ? ? ? ? ? ? step =3}? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }//括弧結(jié)束后的重置工作parsingTf =falsetypes = []? ? ? ? ? ? }? ? ? ? }elseifparsingTf {? ? ? ? ? ? types.append(tk)//todo:返回block類(lèi)型會(huì)使用.設(shè)置值的方式,目前獲取用過(guò)方法方式?jīng)]有.這種的解析,暫時(shí)作為iftk ==Sb.upArrow {? ? ? ? ? ? ? ? mtd.returnTypeBlockTf =true}? ? ? ? }elseiftk ==Sb.colon {? ? ? ? ? ? step =2}elseifstep ==1{iftk =="initWithCoordinate"{//}? ? ? ? ? ? methodParam.name = tk? ? ? ? ? ? step =0}elseifstep ==3{? ? ? ? ? ? methodParam.iName = tk? ? ? ? ? ? step =1mtd.params.append(methodParam)? ? ? ? ? ? mtd.pnameId = mtd.pnameId.appending("\(methodParam.name):")? ? ? ? ? ? methodParam =MethodParam()? ? ? ? }elseiftk !=Sb.minus && tk !=Sb.add {? ? ? ? ? ? methodParam.name = tk? ? ? ? }? ? ? ? ? ? }//遍歷returnmtd}

這個(gè)方法大概的思路就是根據(jù)標(biāo)記符設(shè)置不同的狀態(tài),然后將獲取的信息放入定義的結(jié)構(gòu)中。

使用過(guò)的方法的解析 ??

進(jìn)行使用過(guò)的方法解析前需要處理的事情

@“…” 里面的數(shù)據(jù),因?yàn)檫@里面是允許我們定義的標(biāo)識(shí)符出現(xiàn)的。

遞歸出文件中 import 所有的類(lèi),根據(jù)對(duì)類(lèi)的使用可以清除無(wú)用的 import

繼承鏈的獲取。

解析獲取實(shí)例化了的成員變量列表。在解析時(shí)需要依賴(lài)列表里的成員變量名和變量的類(lèi)進(jìn)行方法的完整獲取。

簡(jiǎn)單的方法

[view update:status animation:YES];

從左到右按照 : 符號(hào)獲取

方法嵌套調(diào)用,下面這種情況如何解析出

@weakify(self);[[[[[[SMNetManager shareInstance] fetchAllFeedWithModelArray:self.feeds] map:^id(NSNumber*value) {? ? @strongify(self);NSUIntegerindex = [value integerValue];self.feeds[index] = [SMNetManager shareInstance].feeds[index];returnself.feeds[index];}] doCompleted:^{//抓完所有的feeds@strongify(self);NSLog(@"fetch complete");//完成置為默認(rèn)狀態(tài)self.tbHeaderLabel.text =@"";self.tableView.tableHeaderView = [[UIViewalloc] init];self.fetchingCount =0;? ? [UIApplicationsharedApplication].networkActivityIndicatorVisible =NO;//下拉刷新關(guān)閉[self.tableView.mj_header endRefreshing];//更新列表[self.tableView reloadData];//檢查是否需要增加源if([SMFeedStore defaultFeeds].count >self.feeds.count) {self.feeds = [SMFeedStore defaultFeeds];? ? ? ? [selffetchAllFeeds];? ? }}] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(SMFeedModel *feedModel) {//抓完一個(gè)@strongify(self);self.tableView.tableHeaderView =self.tbHeaderView;//顯示抓取狀態(tài)self.fetchingCount +=1;self.tbHeaderLabel.text = [NSStringstringWithFormat:@"正在獲取%@...(%lu/%lu)",feedModel.title,(unsignedlong)self.fetchingCount,(unsignedlong)self.feeds.count];? ? [self.tableView reloadData];}];

一開(kāi)始會(huì)想到使用遞歸,以前我做?STMAssembleView?時(shí)就是使用的遞歸,這樣時(shí)間復(fù)雜度就會(huì)是 O(nlogn) ,這次我換了個(gè)思路,將復(fù)雜度降低到了 n ,思路大概是 創(chuàng)建一個(gè)字典,鍵值就是深度,從左到右深度的增加根據(jù)?[?符號(hào),減少根據(jù)?]?符號(hào),值會(huì)在?[時(shí)創(chuàng)建一個(gè)?Method?結(jié)構(gòu)體,根據(jù)]來(lái)完成結(jié)構(gòu)體,將其添加到?methods?數(shù)組中 。

具體實(shí)現(xiàn)如下

classfuncparsing(contentArr:Array,inMethod:Method) ->Method{varmtdIn = inMethod//處理用過(guò)的方法//todo:還要過(guò)濾@""這種情況varpsBrcStep =0varuMtdDic = [Int:Method]()varpreTk =""http://處理?:這種條件判斷簡(jiǎn)寫(xiě)方式varpsCdtTf =falsevarpsCdtStep =0//判斷selectorvarpsSelectorTf =falsevarpreSelectorTk =""varselectorMtd =Method()varselectorMtdPar =MethodParam()? ? ? ? uMtdDic[psBrcStep] =Method()//初始時(shí)就實(shí)例化一個(gè)method,避免在define里定義只定義]符號(hào)forvartkincontentArr {//selector處理ifpsSelectorTf {iftk ==Sb.colon {? ? ? ? ? ? ? ? selectorMtdPar.name = preSelectorTk? ? ? ? ? ? ? ? selectorMtd.params.append(selectorMtdPar)? ? ? ? ? ? ? ? selectorMtd.pnameId +="\(selectorMtdPar.name):"}elseiftk ==Sb.rBktR {? ? ? ? ? ? ? ? mtdIn.usedMethod.append(selectorMtd)? ? ? ? ? ? ? ? psSelectorTf =falseselectorMtd =Method()? ? ? ? ? ? ? ? selectorMtdPar =MethodParam()? ? ? ? ? ? }else{? ? ? ? ? ? ? ? preSelectorTk = tk? ? ? ? ? ? }continue}iftk ==Sb.selector {? ? ? ? ? ? psSelectorTf =trueselectorMtd =Method()? ? ? ? ? ? selectorMtdPar =MethodParam()continue}//通常處理iftk ==Sb.bktL {ifpsCdtTf {? ? ? ? ? ? ? ? psCdtStep +=1}? ? ? ? ? ? psBrcStep +=1uMtdDic[psBrcStep] =Method()? ? ? ? }elseiftk ==Sb.bktR {ifpsCdtTf {? ? ? ? ? ? ? ? psCdtStep -=1}if(uMtdDic[psBrcStep]?.params.count)! >0{? ? ? ? ? ? ? ? mtdIn.usedMethod.append(uMtdDic[psBrcStep]!)? ? ? ? ? ? }? ? ? ? ? ? psBrcStep -=1//[]不配對(duì)的容錯(cuò)處理ifpsBrcStep <0{? ? ? ? ? ? ? ? psBrcStep =0}? ? ? ? ? ? ? ? ? ? }elseiftk ==Sb.colon {//條件簡(jiǎn)寫(xiě)情況處理ifpsCdtTf && psCdtStep ==0{? ? ? ? ? ? ? ? psCdtTf =falsecontinue}//dictionary情況處理@"key":@"value"ifpreTk ==Sb.quotM || preTk =="respondsToSelector"{continue}letprm =MethodParam()? ? ? ? ? ? prm.name = preTkifprm.name !=""{? ? ? ? ? ? ? ? uMtdDic[psBrcStep]?.params.append(prm)? ? ? ? ? ? ? ? uMtdDic[psBrcStep]?.pnameId = (uMtdDic[psBrcStep]?.pnameId.appending("\(prm.name):"))!? ? ? ? ? ? }? ? ? ? }elseiftk ==Sb.qM {? ? ? ? ? ? psCdtTf =true}else{? ? ? ? ? ? tk = tk.replacingOccurrences(of:Sb.newLine, with:"")? ? ? ? ? ? preTk = tk? ? ? ? }? ? }returnmtdIn}

在設(shè)置?Method?結(jié)構(gòu)體時(shí)將參數(shù)名拼接起來(lái)成為?Method?的識(shí)別符用于后面處理時(shí)的快速比對(duì)。

解析使用過(guò)的方法時(shí)有幾個(gè)問(wèn)題需要注意下

1.在方法內(nèi)使用的方法,會(huì)有?respondsToSelector?,?@selector?還有條件簡(jiǎn)寫(xiě)語(yǔ)法的情況需要單獨(dú)處理下。

2.在?#define?里定義使用了方法

#defineCLASS_VALUE(x)? ? [NSValue valueWithNonretainedObject:(x)]

找出無(wú)用方法 ??

獲取到所有使用方法后進(jìn)行去重,和定義方法進(jìn)行匹對(duì)求出差集,即全部未使用的方法。

去除無(wú)用方法 ??

比對(duì)后獲得無(wú)用方法后就要開(kāi)始注釋掉他們了。遍歷未使用的方法,根據(jù)先前?Method結(jié)構(gòu)體中定義了方法所在文件路徑,根據(jù)文件集結(jié)構(gòu)和File的結(jié)構(gòu)體,可以避免 IO ,直接獲取方法對(duì)應(yīng)的文件內(nèi)容和路徑。

對(duì)文件內(nèi)容進(jìn)行行切割,逐行檢測(cè)方法名和參數(shù),匹對(duì)時(shí)開(kāi)始對(duì)行加上注釋?zhuān)?h 文件已;符號(hào)為結(jié)束, m 文件會(huì)對(duì)大括號(hào)進(jìn)行計(jì)數(shù),逐行注釋。實(shí)現(xiàn)的方法具體如下:

//刪除指定的一組方法classfuncdelete(methods:[Method]){print("無(wú)用方法")foraMethodinmethods {print("\(File.desDefineMethodParams(paramArr: aMethod.params))")//開(kāi)始刪除//continuevarhContent =""varmContent =""varmFilePath = aMethod.filePathifaMethod.filePath.hasSuffix(".h") {? ? ? ? ? ? hContent =try!String(contentsOf:URL(string:aMethod.filePath)!, encoding:String.Encoding.utf8)//todo:因?yàn)橄忍幚砹薶文件的情況mFilePath = aMethod.filePath.trimmingCharacters(in:CharacterSet(charactersIn:"h"))//去除頭尾字符集mFilePath = mFilePath.appending("m")? ? ? ? }ifmFilePath.hasSuffix(".m") {do{? ? ? ? ? ? ? ? mContent =tryString(contentsOf:URL(string:mFilePath)!, encoding:String.Encoding.utf8)? ? ? ? ? ? }catch{? ? ? ? ? ? ? ? mContent =""}? ? ? ? }lethContentArr = hContent.components(separatedBy:CharacterSet.newlines)letmContentArr = mContent.components(separatedBy:CharacterSet.newlines)//print(mContentArr)//----------------h文件------------------varpsHMtdTf =falsevarhMtds = [String]()varhMtdStr =""varhMtdAnnoStr =""varhContentCleaned =""forhOneLineinhContentArr {varline = hOneLine.trimmingCharacters(in:CharacterSet.whitespacesAndNewlines)ifline.hasPrefix(Sb.minus) || line.hasPrefix(Sb.add) {? ? ? ? ? ? ? ? psHMtdTf =truehMtds +=self.createOCTokens(conent: line)? ? ? ? ? ? ? ? hMtdStr = hMtdStr.appending(hOneLine +Sb.newLine)? ? ? ? ? ? ? ? hMtdAnnoStr +="http://-----由SMCheckProject工具刪除-----\n//"hMtdAnnoStr += hOneLine +Sb.newLine? ? ? ? ? ? ? ? line =self.dislodgeAnnotaionInOneLine(content: line)? ? ? ? ? ? ? ? line = line.trimmingCharacters(in:CharacterSet.whitespacesAndNewlines)? ? ? ? ? ? }elseifpsHMtdTf {? ? ? ? ? ? ? ? hMtds +=self.createOCTokens(conent: line)? ? ? ? ? ? ? ? hMtdStr = hMtdStr.appending(hOneLine +Sb.newLine)? ? ? ? ? ? ? ? hMtdAnnoStr +="http://"+ hOneLine +Sb.newLine? ? ? ? ? ? ? ? line =self.dislodgeAnnotaionInOneLine(content: line)? ? ? ? ? ? ? ? line = line.trimmingCharacters(in:CharacterSet.whitespacesAndNewlines)? ? ? ? ? ? }else{? ? ? ? ? ? ? ? hContentCleaned += hOneLine +Sb.newLine? ? ? ? ? ? }ifline.hasSuffix(Sb.semicolon) && psHMtdTf{? ? ? ? ? ? ? ? psHMtdTf =falseletmethodPnameId =ParsingMethod.parsingWithArray(arr: hMtds).pnameIdifaMethod.pnameId == methodPnameId {? ? ? ? ? ? ? ? ? ? hContentCleaned += hMtdAnnoStr? ? ? ? ? ? ? ? }else{? ? ? ? ? ? ? ? ? ? hContentCleaned += hMtdStr? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? hMtdAnnoStr =""hMtdStr =""hMtds = []? ? ? ? ? ? }? ? ? ? }//刪除無(wú)用函數(shù)try! hContentCleaned.write(to:URL(string:aMethod.filePath)!, atomically:false, encoding:String.Encoding.utf8)//----------------m文件----------------varmDeletingTf =falsevarmBraceCount =0varmContentCleaned =""varmMtdStr =""varmMtdAnnoStr =""varmMtds = [String]()varpsMMtdTf =falseformOneLineinmContentArr {letline = mOneLine.trimmingCharacters(in:CharacterSet.whitespacesAndNewlines)ifmDeletingTf {letlTokens =self.createOCTokens(conent: line)? ? ? ? ? ? ? ? mMtdAnnoStr +="http://"+ mOneLine +Sb.newLinefortkinlTokens {iftk ==Sb.braceL {? ? ? ? ? ? ? ? ? ? ? ? mBraceCount +=1}iftk ==Sb.braceR {? ? ? ? ? ? ? ? ? ? ? ? mBraceCount -=1ifmBraceCount ==0{? ? ? ? ? ? ? ? ? ? ? ? ? ? mContentCleaned = mContentCleaned.appending(mMtdAnnoStr)? ? ? ? ? ? ? ? ? ? ? ? ? ? mMtdAnnoStr =""mDeletingTf =false}? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? }continue}ifline.hasPrefix(Sb.minus) || line.hasPrefix(Sb.add) {? ? ? ? ? ? ? ? psMMtdTf =truemMtds +=self.createOCTokens(conent: line)? ? ? ? ? ? ? ? mMtdStr = mMtdStr.appending(mOneLine +Sb.newLine)? ? ? ? ? ? ? ? mMtdAnnoStr +="http://-----由SMCheckProject工具刪除-----\n//"+ mOneLine +Sb.newLine? ? ? ? ? ? }elseifpsMMtdTf {? ? ? ? ? ? ? ? mMtdStr = mMtdStr.appending(mOneLine +Sb.newLine)? ? ? ? ? ? ? ? mMtdAnnoStr +="http://"+ mOneLine +Sb.newLine? ? ? ? ? ? ? ? mMtds +=self.createOCTokens(conent: line)? ? ? ? ? ? }else{? ? ? ? ? ? ? ? mContentCleaned = mContentCleaned.appending(mOneLine +Sb.newLine)? ? ? ? ? ? }ifline.hasSuffix(Sb.braceL) && psMMtdTf {? ? ? ? ? ? ? ? psMMtdTf =falseletmethodPnameId =ParsingMethod.parsingWithArray(arr: mMtds).pnameIdifaMethod.pnameId == methodPnameId {? ? ? ? ? ? ? ? ? ? mDeletingTf =truemBraceCount +=1mContentCleaned = mContentCleaned.appending(mMtdAnnoStr)? ? ? ? ? ? ? ? }else{? ? ? ? ? ? ? ? ? ? mContentCleaned = mContentCleaned.appending(mMtdStr)? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? mMtdStr =""mMtdAnnoStr =""mMtds = []? ? ? ? ? ? }? ? ? ? }//m文件//刪除無(wú)用函數(shù)ifmContent.characters.count>0{try! mContentCleaned.write(to:URL(string:mFilePath)!, atomically:false, encoding:String.Encoding.utf8)? ? ? ? }? ? }}

完整代碼在:https://github.com/ming1016/SMCheckProject?這里。

后記 ??

有了這樣的結(jié)構(gòu)數(shù)據(jù)就可以模擬更多人工檢測(cè)的方式來(lái)檢測(cè)項(xiàng)目。

通過(guò)獲取的方法結(jié)合獲取類(lèi)里面定義的局部變量和全局變量,在解析過(guò)程中模擬引用的計(jì)數(shù)來(lái)分析循環(huán)引用等等類(lèi)似這樣的檢測(cè)。

通過(guò)獲取的類(lèi)的完整結(jié)構(gòu)還能夠?qū)⑵滢D(zhuǎn)成JavaScriptCore能解析的js語(yǔ)法文件等等。

對(duì)于APP瘦身的一些想法 ??

瘦身應(yīng)該從平時(shí)開(kāi)發(fā)時(shí)就需要注意。除了功能和組件上的復(fù)用外還需要對(duì)堆棧邏輯進(jìn)行封裝以達(dá)到代碼壓縮的效果。

比如使用ReactiveCocoa和RxSwift這樣的函數(shù)響應(yīng)式編程庫(kù)提供的方法和編程模式進(jìn)行

對(duì)于UI的視圖邏輯可以使用一套統(tǒng)一邏輯壓縮代碼使用DSL來(lái)簡(jiǎn)化寫(xiě)法等。

小禮物走一走,來(lái)簡(jiǎn)書(shū)關(guān)注我

贊賞支持

?開(kāi)發(fā)

? 著作權(quán)歸作者所有

舉報(bào)文章

關(guān)注星光社的戴銘?

寫(xiě)了 126812 字,被 3032 人關(guān)注,獲得了 1731 個(gè)喜歡

微博:@戴銘,github帳號(hào)ming1016,喜歡畫(huà)畫(huà),instagram帳號(hào)ming1016,qq:36270359

喜歡


29

更多分享

登錄?后發(fā)表評(píng)論

8條評(píng)論?只看作者

按時(shí)間倒序按時(shí)間正序


寫(xiě)太多bug了

4樓 · 2016.12.30 16:11

bogon:SMCheckProject chengqian$ pod install --no-repo-update

Analyzing dependencies

[!] Unable to satisfy the following requirements:

- `SnapKit (~> 3.0.2)` required by `Podfile`

None of your spec sources contain a spec satisfying the dependency: `SnapKit (~> 3.0.2)`.

You have either:

* out-of-date source repos which you can update with `pod repo update`.

* mistyped the name or version.

* not added the source repo that hosts the Podspec to your Podfile.

Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by default.

??回復(fù)

星光社的戴銘

?@CharType?pod install 一下

2016.12.31 16:14??回復(fù)

寫(xiě)太多bug了

?@星光社的戴銘?謝謝

2017.01.03 21:17??回復(fù)

厚臉皮

?拖入工程目錄之后,查找不出來(lái)

2017.01.22 10:28??回復(fù)

?添加新評(píng)論


楓_LY

3樓 · 2016.11.23 15:05

工程報(bào)錯(cuò), 不能運(yùn)行diff: /../Podfile.lock: No such file or directory

diff: /Manifest.lock: No such file or directory

error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.

??回復(fù)

星光社的戴銘

?@楓_LY?用cocoapods更新下

2016.11.24 10:47??回復(fù)

?添加新評(píng)論


I往往I

2樓 · 2016.10.31 14:37

本人swift知識(shí)尚淺,老是容易在這崩潰ParsingMethodContent中

34行

if (uMtdDic[psBrcStep]?.params.count)! > 0 {

??回復(fù)

星光社的戴銘

?@I往往I?問(wèn)題已修復(fù),可以拉下最新代碼

2016.11.10 19:50??回復(fù)

?添加新評(píng)論

被以下專(zhuān)題收入,發(fā)現(xiàn)更多相似內(nèi)容

移動(dòng)前沿

iOS 實(shí)用技術(shù)

征服iOS

App性能監(jiān)控

項(xiàng)目筆記

電腦相關(guān)

待閱讀的文章

展開(kāi)更多?

Spring Cloud

Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智能路由,微代理,控制總線)。分布式系統(tǒng)的協(xié)調(diào)導(dǎo)致了樣板模式, 使用Spring Cloud開(kāi)發(fā)人員可以快速地支持實(shí)現(xiàn)這些模式的服務(wù)和應(yīng)用程序。他們將在任何分布式...

?卡卡羅2017

百戰(zhàn)程序員V1.2——尚學(xué)堂旗下高端培訓(xùn)_ Java1573題

百戰(zhàn)程序員_ Java1573題 QQ群:561832648489034603 掌握80%年薪20萬(wàn)掌握50%年薪10萬(wàn) 全程項(xiàng)目穿插, 從易到難,含17個(gè)項(xiàng)目視頻和資料持續(xù)更新,請(qǐng)關(guān)注www.itbaizhan.com 國(guó)內(nèi)最牛七星級(jí)團(tuán)隊(duì)馬士兵、高淇等11位十年開(kāi)發(fā)經(jīng)驗(yàn)專(zhuān)...

?Albert陳凱

掘金 Android 文章精選合集

用兩張圖告訴你,為什么你的 App 會(huì)卡頓? - Android - 掘金Cover 有什么料? 從這篇文章中你能獲得這些料: 知道setContentView()之后發(fā)生了什么? ... Android 獲取 View 寬高的常用正確方式,避免為零 - 掘金相信有很多朋友...

?掘金官方

Android - 收藏集?

用兩張圖告訴你,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你能獲得這些料: 知道setContentView()之后發(fā)生了什么? ... Android 獲取 View 寬高的常用正確方式,避免為零 - 掘金 相信有很多...

?passiontim

無(wú)標(biāo)題文章

轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一. Runtime簡(jiǎn)介Runtime 又叫運(yùn)行時(shí),是一套底層的 C 語(yǔ)言 API,是 iOS 系統(tǒng)的核心之一。開(kāi)發(fā)者在編碼過(guò)程中,可以給任意一個(gè)對(duì)象發(fā)送消息,在編...

?色色的小法師

理想主義還是精神?。俊{粹副元首1941年只身飛赴英國(guó)之謎

馮濤 1941年5月10日,納粹德國(guó)副元首魯?shù)婪颉ず账梗≧udolf Hess)飛赴英國(guó)并被俘的消息,如同一枚重磅炸彈在德國(guó)和英國(guó)政府間炸開(kāi)。這一看似瘋狂的事件的來(lái)龍去脈究竟如何?赫斯難道真的如果納粹德國(guó)所宣稱(chēng)的那樣瘋了嗎? “2016年5月20日,大德意志帝國(guó)與大英帝國(guó)與...

?崎峻軍史周刊

短篇 《余路》

1 起床 ,床上粉絲的被子縮成一團(tuán),紅色地毯上都是狗毛和薯片碎屑,在看床上空無(wú)一人,那幾個(gè)酒瓶亂七八糟的在地下。 滋吖一聲,木門(mén)就被打開(kāi)了,隨處傳來(lái)韓冰的獅子哄: 韓鑫!你丫的,腦子有病吧,不在床上睡又跑到地下去了。沒(méi)給你說(shuō)過(guò)么地下咱家寶在換膚呢。地上都是他的舊的皮膚,你還...

?Te小魚(yú)兒er

88/100-謝謝林先生,點(diǎn)亮顆顆心

社會(huì)各界愛(ài)心人士: 6.22火災(zāi),我的三個(gè)孩子和我的妻子永遠(yuǎn)離開(kāi)我了。我的原本幸福美滿的家庭毀于一旦,至今我都不愿相信這一切真的發(fā)生了。接下去的人生該怎么過(guò)?最近這些天,這個(gè)念頭總是在我腦子里回旋。我堅(jiān)信,真相會(huì)被揭示!我也堅(jiān)信,善惡終有報(bào)!為了尋求真相,為了給逝去的親人討...

?桂霏是人才

一句話告訴你十二星座遇到真愛(ài)時(shí)的表現(xiàn)

之前小桃看到一句話:如果我有尾巴的話,看見(jiàn)喜歡的人一定會(huì)止不住的搖。 每個(gè)人遇到真愛(ài)時(shí)的反應(yīng)都不一樣,十二星座不同的人,他們遇到真愛(ài)時(shí)會(huì)怎樣表現(xiàn)呢?一起來(lái)看看吧~ 白羊座 會(huì)認(rèn)真看著你的眼睛說(shuō):“我愛(ài)你” 金牛座 愿意帶你回家 雙子座 主動(dòng)上交工資卡 巨蟹座 纏著你讓你帶他...

?照亮天空星座

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、 前言 如果你對(duì)App優(yōu)化比較敏感,那么Apk安裝包的大小就一定不會(huì)忽視。關(guān)于瘦身的原因,大概有以下幾個(gè)方面:...
    未來(lái)的理想閱讀 11,848評(píng)論 4 40
  • 本文會(huì)不定期更新,推薦watch下項(xiàng)目。如果喜歡請(qǐng)star,如果覺(jué)得有紕漏請(qǐng)?zhí)峤籭ssue,如果你有更好的點(diǎn)子可以...
    天之界線2010閱讀 18,841評(píng)論 19 153
  • 文章最后有我的 12 條小總結(jié)。 寫(xiě)在前面 最近公司需求不多,正好研究一下 App 瘦身的辦法,寫(xiě)了點(diǎn)小總結(jié)。 如...
    Damonwong閱讀 8,226評(píng)論 14 76
  • 分析當(dāng)前ipa的組成 源代碼 通過(guò)生成linkmap文件,分析源代碼生成的編譯文件的大小。在Build Setti...
    斑駁的流年無(wú)法釋?xiě)?/span>閱讀 5,738評(píng)論 0 14
  • 用戶常常避免下載太大的APP,尤其是使用移動(dòng)流量的情況下,而且太大的APP也會(huì)占用更多的內(nèi)存并消耗更多的資源,導(dǎo)致...
    布吉島原住民閱讀 1,380評(píng)論 0 10

友情鏈接更多精彩內(nèi)容