戴銘(iOS開發(fā)課)讀書筆記:0506章節(jié)-編譯提速

原文鏈接:鏈接器:符號是怎么綁定到地址上的?
原文鏈接:App 如何通過注入動態(tài)庫的方式實現(xiàn)極速編譯調(diào)試?


05 章節(jié) 鏈接器:符號是怎么綁定到地址上的?

這篇文章主要介紹鏈接器的相關(guān)知識,從底層找答案,解決項目編譯速度的問題。
由于理論知識為主,我只是把相關(guān)的知識點做總結(jié)梳理,方便自己記憶和學習,如果想要看完整的內(nèi)容還需閱讀原文

一、iOS 為什么使用編譯器?而不是解釋器?

編譯器就是將“一種語言(通常為高級語言)”翻譯為“另一種語言(通常為低級語言)”的程序。

一個現(xiàn)代編譯器的主要工作流程:源代碼 (source code) → 預處理器 (preprocessor) → 編譯器 (compiler) → 目標代碼 (object code) → 鏈接器(Linker) → 可執(zhí)行程序 (executables)

例如 C、C++、Objective-C甚至匯編語言都是上面所提到的高級語言。

iOS之所以不使用解釋器來運行代碼,是因為蘋果公司希望iPhone的執(zhí)行效率更高、運行速度更快。

那么什么是解釋器呢?

解釋器(英語:Interpreter),又譯為直譯器,是一種電腦程序,能夠把高級編程語言一行一行直接轉(zhuǎn)譯運行。解釋器不會一次把整個程序轉(zhuǎn)譯出來,只像一位“中間人”,每次運行程序時都要先轉(zhuǎn)成另一種語言再作運行,因此解釋器的程序運行速度比較緩慢。它每轉(zhuǎn)譯一行程序敘述就立刻運行,然后再轉(zhuǎn)譯下一行,再運行,如此不停地進行下去。

例如:Python、TCL和各種Shell程序一般而言是使用解釋器執(zhí)行的

那么,使用編譯器和解釋器執(zhí)行代碼的特點概括:

  • 采用編譯器生成機器碼執(zhí)行的好處是效率高,缺點是調(diào)試周期長。
  • 解釋器執(zhí)行的好處是編譯調(diào)試方便,缺點是執(zhí)行效率低。
編譯器和解釋器對比
二、iOS開發(fā)使用的什么編譯器?編譯過程如何?

蘋果公司現(xiàn)在使用的編譯器是 LLVM(Xcode 5 之前使用的 GCC)。

編譯的主要過程:
1 LLVM 預處理你的代碼,比如把宏嵌入到對應的位置。
2 預處理后,LLVM 會對代碼進行詞法分析和語法分析,生成 AST 抽象語法樹。
3 最后 AST 會生成一種更接近機器碼的語言 IR。通過 IR 可以生成多份適合不同平臺的機器碼。對于 iOS 系統(tǒng),IR 生成的可執(zhí)行文件就是 Mach-O。

編譯的主要過程
三、iOS系統(tǒng)的鏈接器?編譯時鏈接器做了什么?

LLVM 其實是編譯器工具鏈技術(shù)的一個集合。其中的 lld 項目就是內(nèi)置鏈接器。

鏈接器的作用是完成變量、函數(shù)符號和其地址綁定的任務。

不使用鏈接器會怎么樣?

不使用鏈接器的話,首先你就需要在寫代碼時給每個指令設(shè)好內(nèi)存地址,就好像直接在和不同平臺的機器溝通,可讀性和可維護性很差。其次這會導致代碼和內(nèi)存地址綁定得太早,也就是說你需要針對不同的平臺寫多份代碼,何必呢?

鏈接器為什么要把項目中的多個 Mach-O 文件合并成一個?

因為單個文件的 Mach-O 文件是無法正常運行的,如果運行時碰到調(diào)用在其他文件中實現(xiàn)的函數(shù)的情況時,就會找不到這個調(diào)用函數(shù)的地址,從而無法繼續(xù)執(zhí)行。

最后總結(jié)一下鏈接器對代碼主要做了哪幾件事
1 去項目文件里查找目標代碼文件里沒有定義的變量。
2 掃描項目中的不同文件,將所有符號定義和引用地址收集起來,并放到全局符號表中。
3 計算合并后長度及位置,生成同類型的段驚醒合并,建立綁定。
4 對項目中的不同文件里的變量進行機制地址重定位。
而且,鏈接器可以通過打開 Dead code stripping 開關(guān),來開啟自動去除無用代碼的功能。

四、動態(tài)庫鏈接

在真實的開發(fā)環(huán)境中,我們需要用到很多現(xiàn)成的共用庫(例如 GUI 框架、網(wǎng)絡框架等),這些共用庫又分為靜態(tài)庫和動態(tài)庫兩種:

  • 靜態(tài)庫是編譯時鏈接到你的 Mach-O 文件的,無法動態(tài)加載和更新。
  • 動態(tài)庫是運行時鏈接的庫,并沒有參與 Mach-O 文件的編譯和鏈接。使用 dyld 可以實現(xiàn)動態(tài)加載。

關(guān)于 dyld 的具體作用,你可以查看 原文 或者這篇博客:Dynamic Linking On OS X

簡單總結(jié),dyld 做了什么:
1 先執(zhí)行 Mach-O 文件,根據(jù) Mach-O 文件里的 undefined 的符號加載對應的動態(tài)庫,系統(tǒng)會設(shè)置一個共享緩存來解決加載的遞歸依賴問題。
2 加載后,將 undefined 的符號綁定到動態(tài)庫里對應的地址上。
3 最后再處理 +load 方法,main函數(shù)返回后運行 static terminator。

五、最后

理解了程序從編譯、鏈接、執(zhí)行、動態(tài)庫加載到main函數(shù)執(zhí)行的過程,再分階段的思考和優(yōu)化。

編譯階段:每個文件獨立編譯成 Mach-O 文件等待鏈接。那么編譯器可以根據(jù)你修改的文件范圍來減少編譯,從而提高每次編譯的速度。

鏈接階段:文件越多,鏈接器所需執(zhí)行的遍歷操作就會越多,從而降低編譯速度。

動態(tài)庫加載階段:在修改代碼之后,嘗試不去鏈接項目中的所有文件,只編譯當前修改的文件動態(tài)庫,通過運行時加載動態(tài)庫的方式達到及時更新的效果。甚至通過逆向的思維,直接將別人的功能模塊作為動態(tài)庫加載到自己的app中。

06 章節(jié) App 如何通過注入動態(tài)庫的方式實現(xiàn)極速編譯調(diào)試?


這個章節(jié)就是對于上篇文章,在最后的 動態(tài)庫加載階段 通過注入動態(tài)庫的方式實現(xiàn)極速編譯。

原文主要介紹了一款名為 Injection for Xcode 的開源工具的使用和原理。

開源地址:https://github.com/johnno1962/InjectionIII
同時我也找到了作者發(fā)布的視頻演示案例:http://artsy.github.io/blog/2016/03/05/iOS-Code-Injection/

你可以快速嘗試:
1 在App Store下載InjectionIII。
2 打開Injectionlll,選擇打開項目的根目錄。
3 在項目 AppDelegate 的 applicationDidFinishLaunching(_ application: UIApplication) 加入下面代碼:

#if DEBUG
Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle")?.load()
#endif

Xcode 10.1:

#if DEBUG
Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection10.bundle")?.load()
#endif

4 在需要變更的控制器中添加監(jiān)聽

NotificationCenter.default.addObserver(self, selector: #selector(ViewController.injected(_:)), name: Notification.Name(rawValue: "INJECTION_BUNDLE_NOTIFICATION"), object: nil)

5 實現(xiàn) injected: 方法,你可以在這里修改代碼:

@objc func injected(_ notification: NSNotification) {
    // to do ...
}

接下來,你的每次 command + s 都可以觸發(fā) injection watching了。

最后分享一張 Injection 的工作原理圖,幫助大家理解。


這部分的內(nèi)容太過于底層了,需要大量的時間去消化和理解。先做到了解相關(guān)的知識,以后有時間再慢慢消化。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、溫故而知新 1. 內(nèi)存不夠怎么辦 內(nèi)存簡單分配策略的問題地址空間不隔離內(nèi)存使用效率低程序運行的地址不確定 關(guān)于...
    SeanCST閱讀 8,117評論 0 27
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,626評論 1 32
  • 前言 說到動態(tài)庫,就不得不提靜態(tài)庫。靜態(tài)庫可以看做是一個具有特定功能的代碼塊,如果app中引用了靜態(tài)庫,則在編譯時...
    wangzzzzz閱讀 5,454評論 6 13
  • 這是冬夜。我把他拎起 放在膝蓋,打開 木盆中的洗腳水,一忽兒熱,一忽兒冷 我感覺到冷 書一頁一頁翻過去, 木盆上空...
    汪繼先閱讀 460評論 0 2
  • 時間是挺快的,回憶一些片段,如在眼前,但分明過去了很遠。比如玲珊說要買房子,原來是兩個月前的事了。比如阿貴上一次來...
    更向遠行閱讀 246評論 0 1

友情鏈接更多精彩內(nèi)容