看了這篇,面試官問你APP體積優化不再用WTF了

long time no see,最近在總結一些平(應)常(付)用(面)到(試)的知識點,今天就跟你們聊了聊App體積優化這個事兒。python

1.爲何要作體積瘦身

別問!問就是爲了應付面試。git

哈哈,開個玩笑。你們生活中都會遇到一個場景,在某個須要緊急打開App的時候,發現使用的App半天打不開!WTF!而另一款相同功能的App卻能夠瞬間打開。哪一個App可以挽留更多的用戶就不言而喻了吧!github

借用某個遊戲裏邊人物的一句話:"時間就是金錢,個人朋友!"面試

2.咱們都能幹什麼?

下邊咱們先查看一個的思惟導圖:bash

思惟導圖已經總結目前我已經知道的而且能夠落地的優化方式。工具

如圖所示APP體積優化包括兩部分:資源瘦身和代碼瘦身。post

下面我將使用APPReduction這個簡單的demo實地操做一下。須要的同窗能夠到gayhub下載一下。測試

3. 具體實施

3.1 資源瘦身

  • 刪除資源優化

    由於曠日持久的業務代碼堆砌,工程內極可能會堆積許多無用的圖片,而這些圖片卻能實實在在的增長App的體積。而咱們徹底能夠藉助工具LSUnusedResources進行資源文件的刪除工做。ui

RedutionViewController中,configImageTest方法中你會找到image圖片的代碼調用

- (void)configImageTest{
    
    [UIImage imageNamed:@"cooker"];
    [UIImage imageNamed:@"driver"];
    [UIImage imageNamed:@"function"];
    [UIImage imageNamed:@"header"];
//    [UIImage imageNamed:[@"think_"stringByAppendingFormat:@"005"]];
//    [UIImage imageNamed:[@"think_"stringByAppendingFormat:@"006"]];
//    [UIImage imageNamed:[@"think_"stringByAppendingFormat:@"007"]];
    
}
複製代碼

當咱們使用LSUnusedResources進行圖片資源檢察的時候會發現未使用的圖片。

可是這裏須要注意最好進行一下手動的檢察避免出現誤刪的狀況,而且若是代碼僅僅是註釋掉,程序並不會認爲資源是廢棄的。

  • 壓縮資源

    請以合理且合法的方式多跟UI多談談,在切圖片的時候請按須要切圖,沒必要要每一張都是高清無碼,在可接受的範圍內能夠壓縮資源圖片!並且筆者認爲全部跟圖片相關的改變質量的問題都須要經手UI,切記不要本身瞎搞。

  • 大圖片資源

    不常常用到的大圖資源能夠採起下載的方式加載到APP上,不是非要打包到ipa裏邊!能跟產品聊聊的問題別死磕代碼

3.2 代碼瘦身

當咱們的App被打包成ipa的時候,代碼會被打包成一個一個個的.o文件,而這些.o文件組成了MachO,而系統在編譯MachO文件的時候會生成一個附帶的文件LinkMap。

3.2.1 LinkMap

  • LinkMap的組成

    LinkMap由Object File、Section、Symbol三部分組成,描述了工程全部代碼的信息。能夠根據這些信息針對性的去優化空間。

  • LinkMap的獲取

    1.在XCode中開啓編譯選項Write Link Map File \nXCode -> Project -> Build Settings -> 把Write Link Map File設置爲YES

    2.在XCode中開啓編譯選項Write Link Map File \nXCode -> Project -> Build Settings -> 把Path to Link Map File的地方設置好地址

    3.運行項目在地址位置將生成.txt的文件

  • LinkMap的分析

    1.藉助工具LinkMap解析工具,咱們能夠分析每一個類佔用的大小

2.針對性的進行代碼的體積的優化,好比三方庫佔用空間巨量,有沒其餘的替代方案。在取捨兩個相同庫的時候也能夠根據體積的比重作出取捨。

看到這裏咱們已經能夠從宏觀的角度上獲取到須要優化哪些部分的代碼,可是微觀角度哪些是無用的類哪些是無用的方法,須要咱們進一步從MachO的層面上去分析。

3.2.2 MachO分析

MachO文件能夠說是App編譯以後的最重要的部分,經過MachOView這個軟件咱們能夠更加直觀看到MachO的組成。若是你的MachOView運行的時候出現崩潰請按照這篇文章進行修改

  • MachO的組成

__objc_selrefs:記錄了幾乎全部被調用的方法

__objc_classrefs和__objc_superrefs:記錄了幾乎全部使用的類

__objc_classlist:工程裏全部的類的地址

  • 刪除無用的類

    MachO文件中__objc_classrefs段記錄裏了引用類的地址,__objc_classlist段記錄了全部類的地址,咱們能夠認爲取二者的差值就能夠得到未使用類的地址,而後進行符號化,就能夠取得未使用類的信息。

    你們可使用classunref這個工具實現未使用類的查找。

    若是對實現感興趣的同窗能夠拜讀大佬的文章iOS代碼瘦身實踐:刪除無用的類

  • 刪除未使用的方法

    當咱們將無用的類刪除完畢以後,在已經使用的類裏邊頗有可能依然會有未使用的方法。

    前邊咱們已經提到過LinkMap中保存了工程的信息,而咱們全部已經被包含到項目中的方法能夠經過LinkMap獲取。

    classunref的啓發下,筆者利用python實現了未使用方法的自動化方式

    由於py實在不太熟悉加上筆者比較懶,請你們忽略一些語法、接口設計不規範等等的問題

    1.使用指令grep '[+|-]\[.*\s.*\]' xxx-linkMap.txt指令咱們獲得全部被包含到工程到項目中的代碼

    // 獲取全部的方法
    def method_readRealization_pointers(linkMapPath,path):
    # all method
    lines = os.popen("grep '[+|-]\[.*\s.*\]' %s" % linkMapPath).readlines()
    // 須要忽略的方法
    lines = method_ignore(lines,path);
    pointers = set()
    for line in lines:
        line = line.split('-')[-1].split('+')[-1].replace("\n","")
        line = line.split(']')[0]
        line = str("%s]"%line)
        pointers.add(line)
    if len(pointers) == 0:
        exit('Finish:method_readRealization_pointers null')
    print("Get all method linkMap pointers...%d"% len(pointers))
    return pointers
    複製代碼

    2.考慮到你們項目中使用了大量的三方庫,而三方庫的方法有許多並未使用,因此經過method_ignore方法進行忽略,這樣獲取的差值的集合中就不會包括三方庫的未使用方法

    def method_ignore(lines,path):
      print("Get method_ignore...")
      effective_symbols = set()
      // 獲取全部須要忽略類名的前綴(例如YYModel 會之前綴YY的方式做出忽略)
      prefixtul = tuple(class_allIgnore_Prefix(path,'',''))
      getPointer = set()
      
      // 此處是爲了忽略Setter Getter方法
      for line in lines:
          classLine = line.split('[')[-1].upper()
          methodLine = line.split(' ')[-1].upper()
          if methodLine.startswith('SET'):
             endLine = methodLine.replace("SET","").replace("]","").replace("\n","").replace(":","")
             print("methodLine:%s endLine:%s"%(methodLine.lower(),endLine.lower()))
             if len(endLine) != 0:
                getPointer.add(endLine)
      getPointer = list(set(getPointer))
      getterTul = tuple(getPointer)
      
      for line in lines:
          classLine = line.split('[')[-1].upper()
          methodLine = line.split(' ')[-1].upper()
          if (classLine.startswith(prefixtul)or methodLine.startswith(prefixtul)  or methodLine.startswith('SET') or methodLine.startswith(getterTul)):
              continue
          effective_symbols.add(line)
      
      if len(effective_symbols) == 0:
          exit('Finish:method_ignore null')
      return effective_symbols;
    
    複製代碼

    3.使用指令otool -v -s __DATA__objc_selrefs指令咱們能夠獲得全部已經被實現的方法

    def method_selrefs_pointers(path):
      # all use methods
      lines = os.popen('/usr/bin/otool -v -s __DATA __objc_selrefs %s' % path).readlines()
      pointers = set()
      for line in lines:
           line = line.split('__TEXT:__objc_methname:')[-1].replace("\n","").replace("_block_invoke","")
           pointers.add(line)
      print("Get use method selrefs pointers...%d"% len(pointers))
      return pointers
      
    複製代碼

    3.利用步驟1和步驟2的差值能夠獲取到還未使用的方法。

    def method_remove_Realization(selrefsPointers,readRealizationPointers):
      if len(selrefsPointers) == 0:
         return readRealizationPointers
      if len(readRealizationPointers) == 0:
         return null
      methodPointers = set()
      for readRealizationPointer in readRealizationPointers:
          newReadRealizationPointer = readRealizationPointer.split(' ')[-1].replace("]","")
          methodPointers.add(newReadRealizationPointer)
      unUsePointers = methodPointers - selrefsPointers;
    
      dict = {}
      for unUsePointer in unUsePointers:
          dict[unUsePointer] = unUsePointer
      
      for readRealizationPointer in readRealizationPointers:
          newReadRealizationPointer = readRealizationPointer.split(' ')[-1].replace("]","")
          if dict.has_key(newReadRealizationPointer):
              dict[newReadRealizationPointer] = readRealizationPointer
              str = dict[newReadRealizationPointer]
      
      return list(dict.values())
    
    複製代碼

    感興趣的同窗能夠在這裏下載到修改版的 ONLClassMethodUnref

3.2.3 AppCode

若是你的工程不夠巨大,藉助AppCode這個工具的靜態分析也能夠查找到未使用的代碼。方法極爲簡單打開AppCode->選擇Code->點擊Inspect Code---等待靜態分析

可是筆者仍是在這裏須要做出提醒,即便你用到了上邊的全部方式,基於動態語言的特性,咱們仍然不可以找出全部未使用的代碼,而且在刪除代碼的時候仍然須要當心翼翼!切勿多刪!記得作迴歸測試!

參考資料

1.iOS代碼瘦身實踐:刪除無用的類

2.MachOView運行的時候出現崩潰請按照這篇文章進行修改

相關文章
相關標籤/搜索