👨🏻💻 Github Demohtml
整理學習 iOS Principle 一系列的文章,每篇開頭歸結知識點,幫助記憶前端
2000年,伊利諾伊大學厄巴納-香檳分校(University of Illinois at Urbana-Champaign 簡稱UIUC)這所享有世界聲望的一流公立研究型大學的 Chris Lattner(他的 twitter @clattner_llvm ) 開發了一個叫做 Low Level Virtual Machine 的編譯器開發工具套件,後來涉及範圍愈來愈大,能夠用於常規編譯器,JIT編譯器,彙編器,調試器,靜態分析工具等一系列跟編程語言相關的工做,因而就把簡稱 LLVM 這個簡稱做爲了正式的名字。Chris Lattner 後來又開發了 Clang,使得 LLVM 直接挑戰 GCC 的地位。2012年,LLVM 得到美國計算機學會 ACM 的軟件系統大獎,和 UNIX,WWW,TCP/IP,Tex,JAVA 等齊名。linux
Chris Lattner 生於 1978 年,2005年加入蘋果,將蘋果使用的 GCC 全面轉爲 LLVM。2010年開始主導開發 Swift 語言。git
iOS 開發中 Objective-C 是 Clang / LLVM 來編譯的。github
Swift 是 Swift / LLVM,其中 Swift 前端會多出 SIL optimizer,它會把 .swift 生成爲中間代碼 .sil 屬於 High-Level IR, 由於 Swift 在編譯時就完成了方法綁定,直接經過地址調用屬於強類型語言,方法調用再也不是像OC那樣的消息發送,這樣編譯就能夠得到更多的信息用在後面的後端優化上。正則表達式
LLVM是一個模塊化和可重用的編譯器和工具鏈技術的集合,Clang 是 LLVM 的子項目,是 C,C++ 和 Objective-C 編譯器,目的是提供驚人的快速編譯,比 GCC 快3倍,其中的 clang static analyzer 主要是進行語法分析,語義分析和生成中間代碼,固然這個過程會對代碼進行檢查,出錯的和須要警告的會標註出來。LLVM 核心庫提供一個優化器,對流行的 CPU 作代碼生成支持。lld 是 Clang / LLVM 的內置連接器,clang 必須調用連接器來產生可執行文件。objective-c
這裏是 Clang 官方詳細文檔: Welcome to Clang’s documentation! — Clang 4.0 documentation 編程
這篇是對 LLVM 架構的一個概述: The Architecture of Open Source Applicationsswift
將編譯器以前對於編譯的前世此生也是須要了解的,好比回答下這個問題,編譯器程序是用什麼編譯的?看看 《linkers and loaders》這本書就知道了。後端
LLVM 是 Low Level Virtual Machine 的簡稱,這個庫提供了與編譯器相關的支持,可以進行程序語言的編譯期優化、連接優化、在線編譯優化、代碼生成。簡而言之,能夠做爲多種語言編譯器的後臺來使用。若是這樣還比較抽象的話,介紹下 Clang 就知道了:Clang 是一個 C++ 編寫、基於 LLVM、發佈於 LLVM BSD 許可證下的 C/C++/Objective C/Objective C++ 編譯器,其目標(之一)就是超越 GCC。
Clang 開發事出有因,Wiki 介紹以下:
Apple 使用 LLVM 在不支持所有 OpenGL 特性的 GPU (Intel 低端顯卡) 上生成代碼 (JIT),令程序仍然可以正常運行。以後 LLVM 與 GCC 的集成過程引起了一些不快,GCC 系統龐大而笨重,而 Apple 大量使用的 Objective-C 在 GCC 中優先級很低。此外 GCC 做爲一個純粹的編譯系統,與 IDE 配合不好。加之許可證方面的要求,Apple 沒法使用修改版的 GCC 而閉源。因而 Apple 決定從零開始寫 C family 的前端,也就是基於 LLVM 的 Clang 了。
Clang 的特性:
固然,GCC 也有其優點:
通常能夠將編程語言分爲兩種,編譯語言和直譯式語言。
iOS開發目前的經常使用語言是:Objective和Swift。兩者都是編譯語言,換句話說都是須要編譯才能執行的。兩者的編譯都是依賴於Clang + LLVM.
無論是OC仍是Swift,都是採用Clang做爲編譯器前端,LLVM(Low level vritual machine)做爲編譯器後端。因此簡單的編譯過程如圖
編譯器前端的任務是進行:語法分析,語義分析,生成中間代碼(intermediate representation )。在這個過程當中,會進行類型檢查,若是發現錯誤或者警告會標註出來在哪一行。
編譯器後端會進行機器無關的代碼優化,生成機器語言,而且進行機器相關的代碼優化。iOS的編譯過程,後端的處理以下
當你在XCode中,選擇build的時候(快捷鍵command+B),會執行以下過程
Entitlements:
{
"application-identifier" = "app的bundleid";
"aps-environment" = development;
}
複製代碼
CompileC ClassName.o ClassName.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler
export LANG=en_US.US-ASCII
export PATH="..."
clang -x objective-c -arch x86_64 -fmessage-length=0 -fobjc-arc... -Wno-missing-field-initializers ... -DDEBUG=1 ... -isysroot iPhoneSimulator10.1.sdk -fasm-blocks ... -I 上文提到的文件 -F 所須要的Framework -iquote 所須要的Framework ... -c ClassName.c -o ClassName.o
複製代碼
經過這個編譯的命令,咱們能夠看到
咱們在每次編譯事後,都會生成一個dsym文件。dsym文件中,存儲了16進制的函數地址映射。
在App實際執行的二進制文件中,是經過地址來調用方法的。在App crash的時候,第三方工具(Fabric,友盟等)會幫咱們抓到崩潰的調用棧,調用棧裏會包含crash地址的調用信息。而後,經過dSYM文件,咱們就能夠由地址映射到具體的函數位置。
XCode中,選擇Window -> Organizer能夠看到咱們生成的archier文件
iOS 如何調試第三方統計到的崩潰報告 (http://blog.csdn.net/hello_hwc/article/details/50036323)
或多或少,你都會在第三方庫或者iOS的頭文件中,見到過attribute。
好比
__attribute__ ((warn_unused_result)) //若是沒有使用返回值,編譯的時候給出警告
複製代碼
attribtue 是一個高級的的編譯器指令,它容許開發者指定更更多的編譯檢查和一些高級的編譯期優化。
分爲三種:
語法結構
attribute 語法格式爲:attribute ((attribute-list))
放在聲明分號「;」前面。
好比,在三方庫中最多見的,聲明一個屬性或者方法在當前版本棄用了
@property (strong,nonatomic)CLASSNAME * property __deprecated;
複製代碼
這樣的好處是:給開發者一個過渡的版本,讓開發者知道這個屬性被棄用了,應當使用最新的API,可是被__deprecated的屬性仍然能夠正常使用。若是直接棄用,會致使開發者在更新Pod的時候,代碼沒法運行了。
__attribtue__的使用場景不少,本文只列舉iOS開發中經常使用的幾個:
//棄用API,用做API更新
#define __deprecated __attribute__((deprecated))
//帶描述信息的棄用
#define __deprecated_msg(_msg) __attribute__((deprecated(_msg)))
//遇到__unavailable的變量/方法,編譯器直接拋出Error
#define __unavailable __attribute__((unavailable))
//告訴編譯器,即便這個變量/方法 沒被使用,也不要拋出警告
#define __unused __attribute__((unused))
//和__unused相反
#define __used __attribute__((used))
//若是不使用方法的返回值,進行警告
#define __result_use_check __attribute__((__warn_unused_result__))
//OC方法在Swift中不可用
#define __swift_unavailable(_msg) __attribute__((__availability__(swift, unavailable, message=_msg)))
複製代碼
你必定還見過以下代碼:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
///代碼
#pragma clang diagnostic pop
複製代碼
這段代碼的做用是
經過clang diagnostic push/pop,你能夠靈活的控制代碼塊的編譯選項。
- iOS 合理利用Clang警告來提升代碼質量 (http://blog.csdn.net/Hello_Hwc/article/details/46425503)
所謂預處理,就是在編譯以前的處理。預處理可以讓你定義編譯器變量,實現條件編譯。
好比,這樣的代碼很常見
#ifdef DEBUG
//...
#else
//...
#endif
複製代碼
一樣,咱們一樣也能夠定義其餘預處理變量,在XCode-選中Target-build settings中,搜索proprecess。而後點擊圖中藍色的加號,能夠分別爲debug和release兩種模式設置預處理宏。
好比咱們加上:TestServer,表示在這個宏中的代碼運行在測試服務器
而後,配合多個Target(右鍵Target,選擇Duplicate),單獨一個Target負責測試服務器。這樣咱們就不用每次切換測試服務器都要修改代碼了。
#ifdef TESTMODE
//測試服務器相關的代碼
#else
//生產服務器相關代碼
#endif
複製代碼
一般,若是你使用CocoaPod來管理三方庫,那麼你的Build Phase是這樣子的:
其中:[CP]開頭的,就是CocoaPod插入的腳本。
而這些配置信息都存儲在這個文件(.xcodeprog)裏
到這裏,CocoaPod的原理也就大體搞清楚了,經過修改xcodeproject,而後配置編譯期腳本,來保證三方庫可以正確的編譯鏈接。
一樣,咱們也能夠插入本身的腳本,來作一些額外的事情。好比,每次進行archive的時候,咱們都必須手動調整target的build版本,若是一不當心,就會忘記。這個過程,咱們能夠經過插入腳本自動化。
buildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${PROJECT_DIR}/${INFOPLIST_FILE}")
buildNumber=$(($buildNumber + 1))
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" "${PROJECT_DIR}/${INFOPLIST_FILE}"
複製代碼
這段腳本其實很簡單,讀取當前pist的build版本號,而後對其加一,從新寫入。
使用起來也很簡單:
而後把這段腳本拷貝進去,而且勾選Run Script Only When installing,保證只有咱們在安裝到設備上的時候,纔會執行這段腳本。重命名腳本的名字爲Auto Increase build number
而後,拖動這個腳本的到Link Binary With Libraries下面
腳本化編譯打包對於CI(持續集成)來講,十分有用。iOS開發中,編譯打包必備的兩個命令是:
//編譯成.app
xcodebuild -workspace $projectName.xcworkspace -scheme $projectName -configuration $buildConfig clean build SYMROOT=$buildAppToDir
//打包
xcrun -sdk iphoneos PackageApplication -v $appDir/$projectName.app -o $appDir/$ipaName.ipa
經過info命令,能夠查看到詳細的文檔
info xcodebuild
複製代碼
以前寫的一套基於 Python 的編譯打包腳本 (https://github.com/ReverseScale/AutoBuildScript/blob/master/autobuild.py)
一般,當項目很大,源代碼和三方庫引入不少的時候,咱們會發現編譯的速度很慢。在瞭解了XCode的編譯過程後,咱們能夠從如下角度來優化編譯速度:
咱們須要一個途徑,可以看到編譯的時間,這樣纔能有個對比,知道咱們的優化究竟有沒有效果。
對於XCode 8,關閉XCode,終端輸入如下指令
defaults write com.apple.dt.Xcode ShowBuildOperationDuration YES
複製代碼
而後,重啓XCode,而後編譯,你會在這裏看到編譯時間。
2.1)forward declaration
所謂forward declaration,就是@class CLASSNAME,而不是#import CLASSNAME.h。這樣,編譯器能大大提升#import的替換速度。
2.2)對經常使用的工具類進行打包(Framework/.a)
打包成Framework或者靜態庫,這樣編譯的時候這部分代碼就不須要從新編譯了。
2.3)經常使用頭文件放到預編譯文件裏
XCode的pch文件是預編譯文件,這裏的內容在執行XCode build以前就已經被預編譯,而且引入到每個.m文件裏了。
3.1)Debug模式下,不生成dsym文件
上文提到了,dysm文件裏存儲了調試信息,在Debug模式下,咱們能夠藉助XCode和LLDB進行調試。因此,不須要生成額外的dsym文件來下降編譯速度。
3.2)Debug開啓Build Active Architecture Only
在XCode -> Build Settings -> Build Active Architecture Only 改成YES。這樣作,能夠只編譯當前的版本,好比arm7/arm64等等,記得只開啓Debug模式。這個選項在高版本的XCode中自動開啓了。
3.3)Debug模式下,關閉編譯器優化
編譯器優化
關於 iOS 編譯 Clang LLVM 相關的知識整理參見: 深刻剖析 iOS 編譯 Clang LLVM
此係列文章內容多爲網上資料整理,文章結尾會列出參照連接,若有紕漏歡迎討論🤗
以上文章整理自:https://my.oschina.net/u/2345393/blog/820141,https://linuxtoy.org/archives/llvm-and-clang.html,https://blog.csdn.net/hello_hwc/article/details/53557308,https://github.com/ming1016/study/wiki/深刻剖析-iOS-編譯-Clang---LLVM