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

来源:https://www.cnblogs.com/simplepp/archive/2020/04/20/12738842.html
-Advertisement-
Play Games

long time no see,最近在總結一些平(應)常(付)用(面)到(試)的知識點,今天就跟大家聊了聊App體積優化這個事兒。 1.為什麼要做體積瘦身 別問!問就是為了應付面試。 哈哈,開個玩笑。大家生活中都會遇到一個場景,在某個需要緊急打開App的時候,發現使用的App半天打不開!WTF!而 ...


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

1.為什麼要做體積瘦身

別問!問就是為了應付面試。

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

借用某個游戲裡邊人物的一句話:"時間就是金錢,我的朋友!"

2.我們都能幹什麼?

下邊我們先查看一個的思維導圖:

 

思維導圖已經總結目前我已經知道的並且可以落地的優化方式。

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

下麵我將使用APPReduction這個簡單的demo實地操作一下。需要的同學可以到gayhub下載一下。

3. 具體實施

3.1 資源瘦身

  • 刪除資源

    因為曠日持久的業務代碼堆砌,工程內很可能會堆積許多無用的圖片,而這些圖片卻能實實在在的增加App的體積。而我們完全可以藉助工具LSUnusedResources進行資源文件的刪除工作。

 

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運行的時候出現崩潰請按照這篇文章進行修改

推薦

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言: 最近也在玩資料庫,感覺普通機子搞資料庫,還是差了點,全文查找,慢的要查一分鐘更久。 但是搞cmd5庫很不錯,億級資料庫,毫秒級。 qq 944520563好吧,下麵開始,首先你得需要一個mysql資料庫,推薦 環境 : apmserv5.2.6 php+mysql Navicat for M ...
  • # Android Sugar ORM (2) ### Android Sugar ORM 實體 #### 1. 創建一個實體類 `Sugar ORM`在創建一個實體的時候, 僅需要使這個實體類繼承於`SugarRecord`即可 ```java public class Book extends ... ...
  • 很簡單,只需要新建一個 drawable 文件 效果概念拓展stroke 是用來處理邊框的,可以修改邊框粗細 和 顏色等等; corners 可以設置邊框的圓角顯示,也可以只是設置某一個圓角; android:bottomLeftRadius -> 設置左下圓角; android:bottomRig... ...
  • 引言 目前經濟增速的放緩,到處都在鼓吹互聯網發展進入下半場。今年跳槽季的遭遇想必大家也是感受到了一絲寒意。筆者有一個朋友在阿裡工作,今年3月底開始請他幫忙內推,也許是阿裡的大前端戰略,也許真的是互聯網的寒意。對於一個三年的iOSer上海這邊一直沒有合適的崗位可推,即便是有也是招P7級別,統統被拒的命 ...
  • iOS 企業賬號配置InHouse類型證書、配置文件流程 一、關於企業賬號 蘋果的開發者賬號分為三種:個人開發者賬號、公司開發者賬號、企業開發者賬號 個人開發者賬號:以個人的名義申請的開發者賬號,有AppleID即可申請;可以在AppStore上發佈應用;可以打包內測版安裝包,需要提供安裝設備的UD ...
  • 解決:UIBarButtonItem size issue.尺寸 ...
  • SDK開發中我們可能希望使用已有的第三方開源庫,比如在發送請求的功能上我們更希望用AFNetworking而非直接使用NSURLSession,又如在實現socket連接時我們更希望用SocketRocket而非自己從零實現。但如果我們直接把AFNetworking的源文件拖到靜態庫SDK里,而宿主 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...