使用Swift3開發(fā)了個MacOS的程序可以檢測出objc項目中無用方法杆怕,然后一鍵全部清理

當(dāng)項目越來越大族购,引入第三方庫越來越多壳贪,上架的 APP 體積也會越來越大,對于用戶來說體驗必定是不好的寝杖。在清理資源违施,編譯選項優(yōu)化,清理無用類等完成后朝墩,能夠做而且效果會比較明顯的就只有清理無用函數(shù)了醉拓。

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

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

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

這幾個現(xiàn)有方案有些比較麻煩的地方街氢,因為檢索出的無用方法沒法確定能夠直接刪除,還需要挨個檢索人工判斷是否可以刪除睦袖,這樣每次要清理時都需要這樣人工排查一遍是非常耗時耗力的珊肃。

這樣就只有模擬編譯過程對代碼進行深入分析才能夠找出確定能夠刪除的方法。具體效果可以先試試看馅笙,程序代碼在:https://github.com/ming1016/SMCheckProject 選擇工程目錄后程序就開始檢索無用方法然后將其注釋掉伦乔。

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

首先確定結(jié)構(gòu),類似先把 OC 文件根據(jù)語法畫出整體結(jié)構(gòu)董习。先看看 OC Runtime 里是如何設(shè)計的結(jié)構(gòu)體烈和。

struct objc_object {  
    Class isa  OBJC_ISA_AVAILABILITY;
};

/*類*/
struct objc_class {  
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class;
    const char *name;
    long version;
    long info;
    long instance_size;
    struct objc_ivar_list *ivars;
    struct objc_method_list **methodLists;
    struct objc_cache *cache;
    struct objc_protocol_list *protocols;
#endif
};

/*成員變量列表*/
struct objc_ivar_list {
    int ivar_count               
#ifdef __LP64__
    int space                    
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]
}      

/*成員變量結(jié)構(gòu)體*/
struct objc_ivar {
    char *ivar_name
    char *ivar_type
    int ivar_offset
#ifdef __LP64__
    int space      
#endif
}    

/*方法列表*/
struct objc_method_list {  
    struct objc_method_list *obsolete;
    int method_count;

#ifdef __LP64__
    int space;
#endif
    /* variable length structure */
    struct objc_method method_list[1];
};

/*方法結(jié)構(gòu)體*/
struct objc_method {  
    SEL method_name;
    char *method_types;    /* a string representing argument/return types */
    IMP method_imp;
};

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

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

//文件
class File: NSObject {
    //文件
    public var type = FileType.FileH
    public var name = ""
    public var content = ""
    public var methods = [Method]() //所有方法
    public var imports = [Import]() //引入類
}

//引入
struct Import {
    public var fileName = ""
}

//對象
class Object {
    public var name = ""
    public var superObject = ""
    public var properties = [Property]()
    public var methods = [Method]()
}

//成員變量
struct Property {
    public var name = ""
    public var type = ""
}

struct Method {
    public var classMethodTf = false //+ or -
    public var returnType = ""
    public var returnTypePointTf = false
    public var returnTypeBlockTf = false
    public var params = [MethodParam]()
    public var usedMethod = [Method]()
    public var filePath = "" //定義方法的文件路徑,方便修改文件使用
    public var pnameId = ""  //唯一標(biāo)識高帖,便于快速比較
}

class MethodParam: NSObject {
    public var name = ""
    public var type = ""
    public var typePointTf = false
    public var iName = ""
}

class Type: NSObject {
    //todo:更多類型
    public var name = ""
    public var type = 0 //0是值類型 1是指針
}
```swift

## 開始語法解析 ??
首先遍歷目錄下所有的文件。
```swift
let fileFolderPath = self.selectFolder()
let fileFolderStringPath = fileFolderPath.replacingOccurrences(of: "file://", with: "")
let fileManager = FileManager.default;
//深度遍歷
let enumeratorAtPath = fileManager.enumerator(atPath: fileFolderStringPath)
//過濾文件后綴
let filterPath = NSArray(array: (enumeratorAtPath?.allObjects)!).pathsMatchingExtensions(["h","m"])

然后將注釋排除在分析之外畦粮,這樣做能夠有效避免無用的解析散址。

分析是否需要按照行來切割乖阵,在 @interface@end@ implementation 预麸, @end 里面不需要換行瞪浸,按照;符號,外部需要按行來吏祸。所以兩種切割都需要对蒲。

先定義語法標(biāo)識符

class Sb: NSObject {
    public static let add = "+"
    public static let minus = "-"
    public static let rBktL = "("
    public static let rBktR = ")"
    public static let asterisk = "*"
    public static let colon = ":"
    public static let semicolon = ";"
    public static let divide = "/"
    public static let agBktL = "<"
    public static let agBktR = ">"
    public static let quotM = "\""
    public static let pSign = "#"
    public static let braceL = "{"
    public static let braceR = "}"
    public static let bktL = "["
    public static let bktR = "]"
    public static let qM = "?"
    public static let upArrow = "^"
    
    public static let inteface = "@interface"
    public static let implementation = "@implementation"
    public static let end = "@end"
    public static let selector = "@selector"
    
    public static let space = " "
    public static let newLine = "\n"
}

接下來就要開始根據(jù)標(biāo)記符號來進行切割分組了,使用 Scanner 贡翘,具體方式如下

//根據(jù)代碼文件解析出一個根據(jù)標(biāo)記符切分的數(shù)組
class func createOCTokens(conent:String) -> [String] {
    var str = conent

    str = self.dislodgeAnnotaion(content: str)

    //開始掃描切割
    let scanner = Scanner(string: str)
    var tokens = [String]()
    //Todo:待處理符號,.
    let operaters = [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]
    var operatersString = ""
    for op in operaters {
        operatersString = operatersString.appending(op)
    }

    var set = CharacterSet()
    set.insert(charactersIn: operatersString)
    set.formUnion(CharacterSet.whitespacesAndNewlines)

    while !scanner.isAtEnd {
        for operater in operaters {
            if (scanner.scanString(operater, into: nil)) {
                tokens.append(operater)
            }
        }

        var result:NSString?
        result = nil;
        if scanner.scanUpToCharacters(from: set, into: &result) {
            tokens.append(result as! String)
        }
    }
    tokens = tokens.filter {
        $0 != Sb.space
    }
    return tokens;
}

行解析的方法

//根據(jù)代碼文件解析出一個根據(jù)行切分的數(shù)組
class func createOCLines(content:String) -> [String] {
    var str = content
    str = self.dislodgeAnnotaion(content: str)
    let strArr = str.components(separatedBy: CharacterSet.newlines)
    return strArr
}

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

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

這里按照語法規(guī)則順序取出即可蹈矮,將方法名,返回類型鸣驱,參數(shù)名泛鸟,參數(shù)類型記錄。這里需要注意 Block 類型的參數(shù)

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

這種類型中還帶有括號的語法的解析踊东,這里用到的方法是對括號進行計數(shù)北滥,左括號加一右括號減一的方式取得完整方法。

獲得這些數(shù)據(jù)后就可以開始檢索定義的方法了闸翅。我寫了一個類專門用來獲得所有定義的方法

class func parsingWithArray(arr:Array<String>) -> Method {
    var mtd = Method()
    var returnTypeTf = false //是否取得返回類型
    var parsingTf = false //解析中
    var bracketCount = 0 //括弧計數(shù)
    var step = 0 //1獲取參數(shù)名再芋,2獲取參數(shù)類型,3獲取iName
    var types = [String]()
    var methodParam = MethodParam()
    //print("\(arr)")
    for var tk in arr {
        tk = tk.replacingOccurrences(of: Sb.newLine, with: "")
        if (tk == Sb.semicolon || tk == Sb.braceL) && step != 1 {
            var shouldAdd = false
            
            if mtd.params.count > 1 {
                //處理這種- (void)initWithC:(type)m m2:(type2)i, ... NS_REQUIRES_NIL_TERMINATION;入?yún)槎鄥?shù)情況
                if methodParam.type.characters.count > 0 {
                    shouldAdd = true
                }
            } else {
                shouldAdd = true
            }
            if shouldAdd {
                mtd.params.append(methodParam)
                mtd.pnameId = mtd.pnameId.appending("\(methodParam.name):")
            }
            
        } else if tk == Sb.rBktL {
            bracketCount += 1
            parsingTf = true
        } else if tk == Sb.rBktR {
            bracketCount -= 1
            if bracketCount == 0 {
                var typeString = ""
                for typeTk in types {
                    typeString = typeString.appending(typeTk)
                }
                if !returnTypeTf {
                    //完成獲取返回
                    mtd.returnType = typeString
                    step = 1
                    returnTypeTf = true
                } else {
                    if step == 2 {
                        methodParam.type = typeString
                        step = 3
                    }
                    
                }
                //括弧結(jié)束后的重置工作
                parsingTf = false
                types = []
            }
        } else if parsingTf {
            types.append(tk)
            //todo:返回block類型會使用.設(shè)置值的方式坚冀,目前獲取用過方法方式?jīng)]有.這種的解析济赎,暫時作為
            if tk == Sb.upArrow {
                mtd.returnTypeBlockTf = true
            }
        } else if tk == Sb.colon {
            step = 2
        } else if step == 1 {
            if tk == "initWithCoordinate" {
                //
            }
            methodParam.name = tk
            step = 0
        } else if step == 3 {
            methodParam.iName = tk
            step = 1
            mtd.params.append(methodParam)
            mtd.pnameId = mtd.pnameId.appending("\(methodParam.name):")
            methodParam = MethodParam()
        } else if tk != Sb.minus && tk != Sb.add {
            methodParam.name = tk
        }
        
    }//遍歷
    
    return mtd
}

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

使用過的方法的解析 ??

進行使用過的方法解析前需要處理的事情

  • @“…” 里面的數(shù)據(jù)联喘,因為這里面是允許我們定義的標(biāo)識符出現(xiàn)的。
  • 遞歸出文件中 import 所有的類辙纬,根據(jù)對類的使用可以清除無用的 import
  • 繼承鏈的獲取豁遭。
  • 解析獲取實例化了的成員變量列表。在解析時需要依賴列表里的成員變量名和變量的類進行方法的完整獲取贺拣。

簡單的方法

[view update:status animation:YES];

從左到右按照 : 符號獲取

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

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

一開始會想到使用遞歸譬涡,以前我做 STMAssembleView 時就是使用的遞歸闪幽,這樣時間復(fù)雜度就會是 O(nlogn) ,這次我換了個思路涡匀,將復(fù)雜度降低到了 n 盯腌,思路大概是 創(chuàng)建一個字典,鍵值就是深度陨瘩,從左到右深度的增加根據(jù) [ 符號腕够,減少根據(jù) ] 符號级乍,值會在 [ 時創(chuàng)建一個 Method 結(jié)構(gòu)體,根據(jù)]來完成結(jié)構(gòu)體帚湘,將其添加到 methods 數(shù)組中 玫荣。

具體實現(xiàn)如下

class func parsing(contentArr:Array<String>, inMethod:Method) -> Method {
    var mtdIn = inMethod
    //處理用過的方法
    //todo:還要過濾@""這種情況
    var psBrcStep = 0
    var uMtdDic = [Int:Method]()
    var preTk = ""
    //處理?:這種條件判斷簡寫方式
    var psCdtTf = false
    var psCdtStep = 0
    //判斷selector
    var psSelectorTf = false
    var preSelectorTk = ""
    var selectorMtd = Method()
    var selectorMtdPar = MethodParam()
    
    uMtdDic[psBrcStep] = Method() //初始時就實例化一個method,避免在define里定義只定義]符號
    
    for var tk in contentArr {
        //selector處理
        if psSelectorTf {
            if tk == Sb.colon {
                selectorMtdPar.name = preSelectorTk
                selectorMtd.params.append(selectorMtdPar)
                selectorMtd.pnameId += "\(selectorMtdPar.name):"
            } else if tk == Sb.rBktR {
                mtdIn.usedMethod.append(selectorMtd)
                psSelectorTf = false
                selectorMtd = Method()
                selectorMtdPar = MethodParam()
            } else {
                preSelectorTk = tk
            }
            continue
        }
        if tk == Sb.selector {
            psSelectorTf = true
            selectorMtd = Method()
            selectorMtdPar = MethodParam()
            continue
        }
        //通常處理
        if tk == Sb.bktL {
            if psCdtTf {
                psCdtStep += 1
            }
            psBrcStep += 1
            uMtdDic[psBrcStep] = Method()
        } else if tk == Sb.bktR {
            if psCdtTf {
                psCdtStep -= 1
            }
            if (uMtdDic[psBrcStep]?.params.count)! > 0 {
                mtdIn.usedMethod.append(uMtdDic[psBrcStep]!)
            }
            psBrcStep -= 1
            //[]不配對的容錯處理
            if psBrcStep < 0 {
                psBrcStep = 0
            }
            
        } else if tk == Sb.colon {
            //條件簡寫情況處理
            if psCdtTf && psCdtStep == 0 {
                psCdtTf = false
                continue
            }
            //dictionary情況處理@"key":@"value"
            if preTk == Sb.quotM || preTk == "respondsToSelector" {
                continue
            }
            let prm = MethodParam()
            prm.name = preTk
            if prm.name != "" {
                uMtdDic[psBrcStep]?.params.append(prm)
                uMtdDic[psBrcStep]?.pnameId = (uMtdDic[psBrcStep]?.pnameId.appending("\(prm.name):"))!
            }
        } else if tk == Sb.qM {
            psCdtTf = true
        } else {
            tk = tk.replacingOccurrences(of: Sb.newLine, with: "")
            preTk = tk
        }
    }
    
    return mtdIn
}

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

解析使用過的方法時有幾個問題需要注意下
1.在方法內(nèi)使用的方法捅厂,會有 respondsToSelector@selector 還有條件簡寫語法的情況需要單獨處理下资柔。
2.在 #define 里定義使用了方法

#define CLASS_VALUE(x)    [NSValue valueWithNonretainedObject:(x)]

找出無用方法 ??

獲取到所有使用方法后進行去重焙贷,和定義方法進行匹對求出差集,即全部未使用的方法建邓。

去除無用方法 ??

比對后獲得無用方法后就要開始注釋掉他們了盈厘。遍歷未使用的方法,根據(jù)先前 Method 結(jié)構(gòu)體中定義了方法所在文件路徑官边,根據(jù)文件集結(jié)構(gòu)和File的結(jié)構(gòu)體沸手,可以避免 IO ,直接獲取方法對應(yīng)的文件內(nèi)容和路徑注簿。
對文件內(nèi)容進行行切割契吉,逐行檢測方法名和參數(shù),匹對時開始對行加上注釋诡渴, h 文件已;符號為結(jié)束捐晶, m 文件會對大括號進行計數(shù),逐行注釋妄辩。實現(xiàn)的方法具體如下:

//刪除指定的一組方法
class func delete(methods:[Method]) {
    print("無用方法")
    for aMethod in methods {
        print("\(File.desDefineMethodParams(paramArr: aMethod.params))")

        //開始刪除
        //continue
        var hContent = ""
        var mContent = ""
        var mFilePath = aMethod.filePath
        if aMethod.filePath.hasSuffix(".h") {
            hContent = try! String(contentsOf: URL(string:aMethod.filePath)!, encoding: String.Encoding.utf8)
            //todo:因為先處理了h文件的情況
            mFilePath = aMethod.filePath.trimmingCharacters(in: CharacterSet(charactersIn: "h")) //去除頭尾字符集
            mFilePath = mFilePath.appending("m")
        }
        if mFilePath.hasSuffix(".m") {
            do {
                mContent = try String(contentsOf: URL(string:mFilePath)!, encoding: String.Encoding.utf8)
            } catch {
                mContent = ""
            }

        }

        let hContentArr = hContent.components(separatedBy: CharacterSet.newlines)
        let mContentArr = mContent.components(separatedBy: CharacterSet.newlines)
        //print(mContentArr)
        //----------------h文件------------------
        var psHMtdTf = false
        var hMtds = [String]()
        var hMtdStr = ""
        var hMtdAnnoStr = ""
        var hContentCleaned = ""
        for hOneLine in hContentArr {
            var line = hOneLine.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)

            if line.hasPrefix(Sb.minus) || line.hasPrefix(Sb.add) {
                psHMtdTf = true
                hMtds += 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)
            } else if psHMtdTf {
                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
            }

            if line.hasSuffix(Sb.semicolon) && psHMtdTf{
                psHMtdTf = false

                let methodPnameId = ParsingMethod.parsingWithArray(arr: hMtds).pnameId
                if aMethod.pnameId == methodPnameId {
                    hContentCleaned += hMtdAnnoStr

                } else {
                    hContentCleaned += hMtdStr
                }
                hMtdAnnoStr = ""
                hMtdStr = ""
                hMtds = []
            }


        }
        //刪除無用函數(shù)
        try! hContentCleaned.write(to: URL(string:aMethod.filePath)!, atomically: false, encoding: String.Encoding.utf8)

        //----------------m文件----------------
        var mDeletingTf = false
        var mBraceCount = 0
        var mContentCleaned = ""
        var mMtdStr = ""
        var mMtdAnnoStr = ""
        var mMtds = [String]()
        var psMMtdTf = false
        for mOneLine in mContentArr {
            let line = mOneLine.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)

            if mDeletingTf {
                let lTokens = self.createOCTokens(conent: line)
                mMtdAnnoStr += "http://" + mOneLine + Sb.newLine
                for tk in lTokens {
                    if tk == Sb.braceL {
                        mBraceCount += 1
                    }
                    if tk == Sb.braceR {
                        mBraceCount -= 1
                        if mBraceCount == 0 {
                            mContentCleaned = mContentCleaned.appending(mMtdAnnoStr)
                            mMtdAnnoStr = ""
                            mDeletingTf = false
                        }
                    }
                }

                continue
            }


            if line.hasPrefix(Sb.minus) || line.hasPrefix(Sb.add) {
                psMMtdTf = true
                mMtds += self.createOCTokens(conent: line)
                mMtdStr = mMtdStr.appending(mOneLine + Sb.newLine)
                mMtdAnnoStr += "http://-----由SMCheckProject工具刪除-----\n//" + mOneLine + Sb.newLine
            } else if psMMtdTf {
                mMtdStr = mMtdStr.appending(mOneLine + Sb.newLine)
                mMtdAnnoStr += "http://" + mOneLine + Sb.newLine
                mMtds += self.createOCTokens(conent: line)
            } else {
                mContentCleaned = mContentCleaned.appending(mOneLine + Sb.newLine)
            }

            if line.hasSuffix(Sb.braceL) && psMMtdTf {
                psMMtdTf = false
                let methodPnameId = ParsingMethod.parsingWithArray(arr: mMtds).pnameId
                if aMethod.pnameId == methodPnameId {
                    mDeletingTf = true
                    mBraceCount += 1
                    mContentCleaned = mContentCleaned.appending(mMtdAnnoStr)
                } else {
                    mContentCleaned = mContentCleaned.appending(mMtdStr)
                }
                mMtdStr = ""
                mMtdAnnoStr = ""
                mMtds = []
            }

        } //m文件

        //刪除無用函數(shù)
        if mContent.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ù)就可以模擬更多人工檢測的方式來檢測項目。

通過獲取的方法結(jié)合獲取類里面定義的局部變量和全局變量眼耀,在解析過程中模擬引用的計數(shù)來分析循環(huán)引用等等類似這樣的檢測英支。
通過獲取的類的完整結(jié)構(gòu)還能夠?qū)⑵滢D(zhuǎn)成JavaScriptCore能解析的js語法文件等等。

對于APP瘦身的一些想法 ??

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

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

對于UI的視圖邏輯可以使用一套統(tǒng)一邏輯壓縮代碼使用DSL來簡化寫法等。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末楞黄,一起剝皮案震驚了整個濱河市池凄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鬼廓,老刑警劉巖肿仑,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡尤慰,警方通過查閱死者的電腦和手機勾邦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來割择,“玉大人,你說我怎么就攤上這事萎河±笥荆” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵虐杯,是天一觀的道長玛歌。 經(jīng)常有香客問我,道長擎椰,這世上最難降的妖魔是什么支子? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮达舒,結(jié)果婚禮上值朋,老公的妹妹穿的比我還像新娘。我一直安慰自己巩搏,他們只是感情好昨登,可當(dāng)我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著贯底,像睡著了一般丰辣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上禽捆,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天笙什,我揣著相機與錄音,去河邊找鬼胚想。 笑死琐凭,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的顿仇。 我是一名探鬼主播淘正,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼臼闻!你這毒婦竟也來了鸿吆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤述呐,失蹤者是張志新(化名)和其女友劉穎惩淳,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡思犁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年代虾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖稻据,靈堂內(nèi)的尸體忽然破棺而出尔当,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響衙傀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜萨咕,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一统抬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧危队,春花似錦聪建、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至盅弛,卻和暖如春钱骂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背挪鹏。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工见秽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人讨盒。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓解取,卻偏偏與公主長得像,于是被迫代替她去往敵國和親返顺。 傳聞我的和親對象是個殘疾皇子禀苦,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)遂鹊,斷路器振乏,智...
    卡卡羅2017閱讀 134,697評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,283評論 25 707
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,725評論 0 9
  • 1 起床 秉扑,床上粉絲的被子縮成一團慧邮,紅色地毯上都是狗毛和薯片碎屑,在看床上空無一人,那幾個酒瓶亂七八糟的在地下误澳。 ...
    Te小魚兒er閱讀 341評論 0 0
  • 社會各界愛心人士: 6.22火災(zāi)忆谓,我的三個孩子和我的妻子永遠(yuǎn)離開我了裆装。我的原本幸福美滿的家庭毀于一旦,至今我都不愿...
    桂霏是人才閱讀 485評論 1 2