由美團文章「一款可讓大型iOS工程編譯速度提高50%的工具」引出的.hmap文件探索(上)

系列文章:OC底層原理系列OC基礎知識系列Swift底層探索系列iOS高級進階系列數組

前言

前段時間,同事給我推薦了一篇美團的文章:一款可讓大型iOS工程編譯速度提高50%的工具,一看標題就以爲驚訝,爲何呢?由於它能讓編譯速度提示50%且不是經過組件二進制化實現,咱們平常的提高編譯速度就是將組件編譯成二進制文件導入項目。本着不清楚的就去了解的原則,就來看看怎麼實現的。xcode

探索

編譯耗時緣由

在項目中咱們會引入頭文件,例以下圖:咱們在ViewController中引入了Person的頭文件 image.png 在咱們引入頭文件的時候,引入的是頭文件的名稱Person,那麼Xcode是怎麼找到這個Person文件實際位置的呢?這就要提到項目中配置的header search path image.png Xcode編譯時候讀取到header search path的地址,而且拼接上咱們引入的頭文件名markdown

也就意味着咱們導入的頭文件分紅兩個部分數據結構

  • 1.前半部分頭文件所在的文件目錄
  • 2.後半部分頭文件名稱

這也就是爲何咱們設置header search path的時候,只須要設置頭文件所在目錄就能夠了。app

問題:由於咱們項目裏有不少文件,那麼咱們就會在header search path設置不少目錄,可是對於找到咱們上面引入一個頭文件Person,他須要查找遍歷全部文件目錄,來找到這個類。這個過程隨着項目的類愈來愈多查找時間就會愈來愈長,就會愈來愈耗時。好比咱們項目組件多達上百個,類有上萬個,那麼這個過程所產生的的耗時就比較明顯了。函數

解決辦法

上面咱們知道項目編譯耗時的緣由,那麼怎麼解決這個問題呢?美團的文章給出答案,就是使用hmap工具

hmap

hmap是什麼呢?美團文章說了它就是Header Map的實體,相似於一個Key-Value的形式Key值頭文件名稱Value頭文件實際物理路徑,其實這個東西一直都存在,只不過咱們沒注意到罷了。post

  • 你們想一下,第一次運行項目或者編譯的時候,會發現很慢,可是一旦運行或者編譯成功後,再次編譯或者運行就會很快,想過爲何沒?
  • 其實第一次編譯後Xcode幫咱們生成一些.hmap文件再次編譯時候會直接使用這些.hmap文件快速找到對應的頭文件,因此編譯速度就會快不少

image.png

咱們看到生成不少.hmap文件Xcode按類別生成的,箭頭指的就是咱們主項目工程.hmap文件,若是咱們對Xcode進行清理,那麼這些.hmap文件也會被清掉,而後咱們就會發現,編譯又慢了起來。學習

經過上面的講解咱們知道.hmap其實就是個容器,它內部確定包含Person文件目錄,那麼就會咱們Xcode查找Person頭文件更快速,那麼有個問題就出來了,咱們本身怎麼去生成.hmap文件呢?.hmap底層結構又是怎樣的呢?測試

探究.hmap文件

咱們編譯一個項目,查看編譯過程,找到ViewController.m文件 image.png

我用紅[]括住,咱們能夠看到它是用-I參數引入了一個.hmap文件,上面咱們也知道Xcode生成多個.hmap,爲了方便你們理解咱們須要讀取下.hmap文件

.hmap文件結構分析

先看下項目目錄 image.png 咱們再看下這個項目生成的.hmap是什麼文件格式 image.png

咱們發現這個裏面包含了項目裏全部的.h,下面咱們來看看.hmap究竟是什麼樣的數據結構

  • 數據結構

咱們能夠經過LLVM來查找相關的內容 image.png

咱們看到有個結構體叫HMapHeader,還有個結構體叫HMapBucket,紅框有兩句話:1.有一個NumBuckets的HMapBucket對象數組緊跟在這個頭文件後面。2.有個字符串跟隨在HMapBucket後面,在StringsOffset

經過上面咱們能夠猜想一下.hmap的結構 image.png

  • 1.最上面的HMapHeader,記錄一些必要信息
  • 2.中間的HMapBucket,有多少個頭文件,就會有多少個HMapBucket,這些都會包裝成HMapBucket
  • 3.字符串裏就是包含着頭文件的前半部分路徑以及後半部分類名的字符串

流程:經過讀取HMapHeader獲取.hmap保存了多少個Bucket,也就知道了這個.hmap保存多少頭文件路徑,而Bucket裏保存了這個頭文件在下面字符串中的偏移量,而後就能夠從最下面的字符串讀取到該頭文件的路徑

讀取.hmap文件

咱們怎麼讀取.hmap信息呢?上面從LLVM中咱們找到hmap的有關結構信息,那麼在LLVM裏面是否有存取相關內容呢?

  • 上面咱們知道結構體信息在Lex文件下找到,那麼讀取信息是否是也在Lex中
  • 最後我找到一個HeaderMapTest文件,感受是測試HeaderMap的文件

image.png

咱們在讀取hmap時,須要用到上面的結構體

下面咱們就來用LLVM獲取的信息,寫一個讀取HeaderMap的插件(咱們在main文件中寫)

hmap讀取

咱們在main函數中寫以下代碼:

  • 斷言宏

image.png

HMAP_HeaderMagicNumber字符串翻轉,由於在HMapHeader結構體有個屬性Magic來表示字節順序,也就是說若是當前的Magic=HMAP_SwappedMagic,也就意味着字節順序是反轉的,也就須要從新交換下字節順序

  • 2.參數判斷非正常文件

image.png

當參數小於兩個的時候(說明沒有傳什麼東西)這個時候就認爲是無效

  • 3.正常文件

image.png

循環經過dump方法導出header map

dump方法

這個方法我是使用C來寫的,由於感受C在處理取文件時更方便些 image.png

傳進來的是文件路徑

  • 1.解析路徑

image.png

解析路徑長度小於0說明路徑不正常

  • 2.獲取MapHeader大小並判斷

image.png

拿到MapHeader大小,若是<0說明MapHeader異常,若是小於實際的MapHeader大小,則說明讀取數據異常

  • 3.判斷字符串是否翻轉,讀取header

image.png

  • 4.獲取桶的數目

image.png

  • 5.獲取桶的數組(指針偏移)

image.png

  • 6.獲取String列表(指針偏移)

image.png

  • 6.遍歷獲取桶,而後取出桶前綴後綴進行拼接

image.png 上面咱們就把一個讀取.hmap的代碼寫好了,下面將以前的項目的.hmap代碼放到這個項目目錄裏,而後在下圖進行設置 image.png

運行項目,打斷點

  • 1.main函數斷點

image.png

第一個是當前可執行文件路徑,第二個是剛纔配置的.hmap路徑

  • 2.查看桶數目

image.png

打印是16個桶,可是不都是頭文件地址(因爲數據對齊的緣由)

  • 3.查看打印數據

image.png

String表有9個數據bucket數目有16個

  • 4.查看結果

image.png

總結

經過上面的讀取打印咱們能夠確認一下幾點:

  • 1.上面說的.hmap是一個key-value形式key是頭文件名
  • 2.prefix保存的是頭文件路徑的前半部分
  • 3.suffix保存的是頭文件路徑的後半部分(頭文件名)
  • 4..hmap是按照對應規則存儲的一堆頭文件

也證實了上面咱們的猜測是對的

擴展

上面寫的代碼能夠生成一個工具,咱們把工具添加到咱們的lldb執行命令裏,這樣咱們就不用上面的方式讀取.hmap文件,咱們就能夠在終端使用命令同樣讀取 image.png

生成本身的.hmap文件

上面說了xcode本身就能主動幫咱們生成.hmap文件,那爲何還須要咱們本身寫呢?美團的文章裏說了,這裏我再簡單的說下:

  • 1.咱們的項目通常都會經過cocoaPods來管理第三方,好比我以前沒事寫的Swift項目引入下面的第三方庫

image.png

  • 2.上面咱們發現以#import "ClassA.h"形式的頭文件,纔會命中.hmap文件不然都將經過Header Search Path尋找其相關路徑

image.png

目錄的問題上面說過它會在多個目錄裏查找一個頭文件是比較耗時,那麼若是我把一個文件路徑放到一個.hmap文件中,那就回快不少。此時若是引入的組件和第三方比較多,那麼勢必會致使編譯速度慢

寫代碼生成本身的.hmap文件

這部分也是個難點,本人也是查看了上面提到的LLVM中的HeaderMapTest.cpp文件,仔細看了下代碼,發現裏面有些生成.hmap代碼,本身寫的代碼比較的簡單,就是爲了說明.hmap是如何生成

  • 1.上面介紹.hmap文件說到,裏面包含不少的Bucket,因此咱們要先生成Bucket

image.png

建立MapFile容器Maker,Maker中包含一個個MapFile,也就是BucketMapFile是一個結構體(HeaderMapTest.cpp中同樣,其中的8表明多少個Bucket750是生成buffer的大小

  • 2.核心代碼,將類名路徑以Bucket形式保存
    • 方法總覽 image.png
    • addString方法 image.png
    • addBucket方法
      image.png

上面的方法都是從LLVM的HeaderMapTest.cpp中找到的

  • 3.將文件導出指定位置
    • 方法總覽 image.png
    • getBuffer方法
      image.png
  • 4.運行項目

image.png

生成了一個TestApp.hmap文件,下面咱們來讀取下這個文件,看看和Xcode生成的是否同樣

  • 5.讀取生成的TestApp.hmap

image.png 和Xcode生成的.hmap image.png

咱們發現生成結果同樣的,下面咱們就去使用下這個本身生成.hmap

  • 6.使用本身生成.hmap

image.png

將Use Header Maps設置爲NO,將Header Search Paths路徑設置成咱們生成的.hmap路徑。因爲寫的項目工程過小,測不出來太大的差異

總結

上面講了.hmap的讀寫方法,看完也就.hmap有個比較清晰的認識了,美團文章解決編譯速度的思路值得咱們去學習,我上面生成.hmap方法其實沒法落地的,就是爲了給你們說一下怎麼去生成一個.hmap美團文章裏說cocoapods-hmap-prebuilt這個插件,我我的感受是一個腳本遍歷頭文件腳本。上面說的生成.hmap方法沒法落地,若是讓它可以落地,就是寫一個腳本遍歷項目以及cocoapods管理第三方庫頭文件將頭文件提取出來用上面方法,最後生成一個.hmap文件,這樣才能落地。這部分也做爲本身的一個技術探索吧,後面有告終果再給你們分享

補充

.hmap已經落地,能夠看下篇文章由美團文章「一款可讓大型iOS工程編譯速度提高50%的工具」引出的.hmap文件(下)hmap落地,文章結尾放出來插件連接

相關文章
相關標籤/搜索