系列文章:OC底層原理系列,OC基礎知識系列,Swift底層探索系列,iOS高級進階系列數組
前段時間,同事給我推薦了一篇美團的文章:一款可讓大型iOS工程編譯速度提高50%的工具,一看標題就以爲驚訝,爲何呢?由於它能讓編譯速度提示50%且不是經過組件二進制化實現
,咱們平常的提高編譯速度
就是將組件編譯成二進制文件導入項目
。本着不清楚的就去了解的原則,就來看看怎麼實現的。xcode
在項目中咱們會引入頭文件,例以下圖:咱們在ViewController中引入了Person的頭文件
在咱們
引入頭文件
的時候,引入的是頭文件的名稱Person
,那麼Xcode是怎麼找到這個Person文件實際位置的呢?這就要提到項目中配置的header search path
Xcode
在編譯
的時候
會讀取到header search path的地址
,而且拼接
上咱們引入的頭文件名
。markdown
也就意味着咱們導入的頭文件
分紅兩個部分
:數據結構
前半部分
:頭文件所在的文件目錄
後半部分
:頭文件名稱
這也就是爲何咱們設置header search path的時候,只須要設置頭文件所在目錄就能夠
了。app
問題:由於咱們項目裏有不少文件
,那麼咱們就會在header search path設置不少目錄
,可是對於找到咱們上面引入一個頭文件Person
,他須要查找遍歷全部
的文件目錄
,來找到這個類
。這個過程隨着項目的類愈來愈多
,查找
的時間
就會愈來愈長
,就會愈來愈耗時
。好比咱們項目組件多達上百個,類有上萬個,那麼這個過程所產生的的耗時就比較明顯了。函數
上面咱們知道項目編譯耗時的緣由,那麼怎麼解決這個問題呢?美團的文章給出答案,就是使用hmap
工具
hmap是什麼呢?美團文章說了它就是Header Map的實體
,相似於一個Key-Value的形式
,Key值
是頭文件
的名稱
,Value
是頭文件
的實際物理路徑
,其實這個東西一直都存在
,只不過咱們沒注意到罷了。post
第一次
運行項目或者編譯的時候,會發現很慢
,可是一旦運行
或者編譯成功
後,再次編譯
或者運行
就會很快
,想過爲何沒?第一次編譯後
,Xcode
就會
幫咱們生成一些.hmap文件
,再次編譯
時候會直接使用
這些.hmap文件快速找到
對應的頭文件
,因此編譯速度就會快不少咱們看到
生成
了不少.hmap文件
,Xcode
是按類別生成
的,箭頭指
的就是咱們主項目工程
的.hmap文件
,若是咱們對Xcode進行清理
,那麼這些.hmap文件
也會被清掉
,而後咱們就會發現,編譯又慢
了起來。學習
經過上面的講解咱們知道.hmap
其實就是個容器
,它內部
確定包含
了Person
的文件目錄
,那麼就會讓
咱們Xcode
在查找Person
的頭文件
時更快
速,那麼有個問題就出來了,咱們本身怎麼去生成.hmap文件
呢?.hmap
的底層結構
又是怎樣的呢?測試
咱們編譯一個項目,查看編譯過程,找到ViewController.m文件
我用
紅[]括住
,咱們能夠看到它是用-I參數
去引入了一個.hmap文件
,上面咱們也知道Xcode
會生成多個.hmap
,爲了方便你們理解咱們須要讀取下.hmap文件
先看下項目目錄 咱們再看下
這個項目生成的.hmap
是什麼文件格式
咱們發現這個裏面
包含了項目裏全部的.h
,下面咱們來看看.hmap
究竟是什麼樣的數據結構
咱們能夠經過LLVM來查找相關的內容
咱們看到有個
結構體叫HMapHeader
,還有個結構體叫HMapBucket
,紅框有兩句話:1.有一個NumBuckets的HMapBucket對象數組緊跟在這個頭文件後面
。2.有個字符串跟隨在HMapBucket後面,在StringsOffset
經過上面咱們能夠猜想一下.hmap的結構
最上面的HMapHeader,記錄一些必要信息
中間的HMapBucket,有多少個頭文件,就會有多少個HMapBucket,這些都會包裝成HMapBucket
字符串裏就是包含着頭文件的前半部分路徑以及後半部分類名的字符串
流程:經過
讀取HMapHeader
,獲取.hmap保存了多少個Bucket
,也就知道了這個.hmap保存
了多少
個頭文件路徑
,而Bucket裏保存
了這個頭文件在下面字符串中的偏移量
,而後就能夠從最下面的字符串
中讀取到該頭文件的路徑
咱們怎麼讀取.hmap信息呢?上面從LLVM
中咱們找到hmap的有關結構信息
,那麼在LLVM裏面是否有存取相關內容呢?
結構體信息
是在Lex文件下
找到,那麼讀取信息是否是也在Lex中HeaderMapTest
的文件
,感受是測試HeaderMap的文件
咱們在
讀取hmap時
,須要用到上面的結構體
下面咱們就來用LLVM獲取的信息,寫一個讀取HeaderMap的插件
(咱們在main文件中寫)
咱們在main函數中寫以下代碼:
HMAP_HeaderMagicNumber
是字符串翻轉
,由於在HMapHeader結構體
中有個屬性Magic來表示字節順序
,也就是說若是當前的Magic=HMAP_SwappedMagic
,也就意味着字節順序是反轉
的,也就須要從新交換下字節順序
當參數小於兩個的時候
(說明沒有傳什麼東西)這個時候就認爲是無效
的
循環經過dump方法導出header map
這個方法我是使用C
來寫的,由於感受C在處理取文件時更方便些
傳進來的是
文件路徑
解析路徑
解析
的路徑長度小於0
說明路徑不正常
獲取MapHeader大小並判斷
拿到
MapHeader大小
,若是<0
則說明MapHeader異常
,若是小於實際的MapHeader大小
,則說明讀取
的數據異常
判斷字符串是否翻轉,讀取header
獲取桶的數目
獲取桶的數組
(指針偏移)獲取String列表
(指針偏移)遍歷獲取桶
,而後取出桶
的前綴
和後綴進行拼接
上面咱們就把一個
讀取.hmap的代碼寫好
了,下面將以前的項目的.hmap代碼放到這個項目目錄裏,而後在下圖進行設置
第一個是
當前可執行文件
的路徑
,第二個是剛纔配置的.hmap路徑
打印是
16個桶
,可是不都是頭文件地址
(因爲數據對齊
的緣由)
String表有9個數據
,bucket數目有16個
經過上面的讀取打印咱們能夠確認一下幾點:
.hmap是一個key-value形式
,key是頭文件名
prefix保存的是頭文件路徑的前半部分
suffix保存的是頭文件路徑的後半部分
(頭文件名).hmap是按照對應規則存儲的一堆頭文件
也證實了上面咱們的猜測是對的
上面寫的代碼能夠生成一個工具,咱們把工具添加
到咱們的lldb執行命令裏
,這樣咱們就不用上面的方式讀取.hmap文件,咱們就能夠在終端使用命令同樣讀取
上面說了xcode
本身就能主動
幫咱們生成.hmap文件
,那爲何還須要咱們本身寫呢?美團的文章裏說了,這裏我再簡單的說下:
經過cocoaPods來管理第三方
,好比我以前沒事寫的Swift項目引入下面的第三方庫以#import "ClassA.h"形式的頭文件
,纔會命中.hmap文件
,不然都將經過Header Search Path尋找其相關路徑
目錄的問題上面說過
它會在多個目錄裏查找一個頭文件是比較耗時
,那麼若是我把一個文件路徑放到一個.hmap文件中
,那就回快不少
。此時若是引入的組件和第三方比較多
,那麼勢必會致使編譯速度慢
。
這部分也是個難點,本人也是查看了上面提到的LLVM中的HeaderMapTest.cpp文件
,仔細看了下代碼,發現裏面有些生成.hmap代碼
,本身寫的代碼比較的簡單
,就是爲了說明.hmap是如何生成
的
.hmap文件
說到,裏面包含不少的Bucket
,因此咱們要先生成Bucket
建立MapFile容器Maker
,Maker中包含一個個MapFile
,也就是Bucket
,MapFile是一個結構體
(HeaderMapTest.cpp中同樣,其中的8表明多少個Bucket
,750是生成buffer的大小
)
類名
和路徑以Bucket
的形式保存
上面的方法都是
從LLVM的HeaderMapTest.cpp中找到的
生成了一個
TestApp.hmap文件
,下面咱們來讀取
下這個文件,看看和Xcode生成的是否同樣
和Xcode生成的.hmap
咱們發現
生成
的結果
是同樣
的,下面咱們就去使用下這個本身生成.hmap
將Use Header Maps設置爲NO,
將Header Search Paths路徑設置成咱們生成的.hmap路徑
。因爲寫的項目工程過小,測不出來太大的差異
。
上面講了.hmap的讀寫方法,看完也就.hmap有個比較清晰的認識了,美團文章解決編譯速度的思路值得咱們去學習
,我上面生成.hmap
的方法
其實沒法落地
的,就是爲了給你們說一下怎麼去生成一個.hmap
,美團文章裏說
的cocoapods-hmap-prebuilt這個插件
,我我的感受是一個腳本
,遍歷頭文件腳本
。上面說的生成.hmap方法沒法落地
,若是讓它可以落地
,就是寫一個腳本
去遍歷項目
以及cocoapods管理
的第三方庫
的頭文件
,將頭文件提取出來
,用上面
的方法
,最後生成
一個.hmap文件
,這樣才能落地
。這部分也做爲本身的一個技術探索吧,後面有告終果再給你們分享
.hmap已經落地,能夠看下篇文章由美團文章「一款可讓大型iOS工程編譯速度提高50%的工具」引出的.hmap文件(下)hmap落地,文章結尾放出來插件連接