C語言相關(guān)內(nèi)容
C語言編譯過程:
- 預處理階段,每當編譯源文件的時候,編譯器首先做的是一些預處理工作。比如預處理器會處理源文件中的宏定義,將代碼中的宏用其對應定義的具體內(nèi)容進行替換。同樣#include的本質(zhì)實際上是刪掉當前行,把*.h的內(nèi)容完全插入到當前行的位置,如果*.h中也使用了類似的宏引入,則會按照同樣的處理方式用各個宏對應的真正代碼進行逐級替代。
- 語法檢查階段
- 編譯階段,以.c文件為單位首先編譯成純匯編語句,再將之匯編成跟 CPU 相關(guān)的二進制碼,生成相對應的目標文件(.obj)。
- 鏈接階段,將各個目標文件中的各段代碼進行絕對地址定位,與C語言庫函數(shù)組合,生成跟特定平臺相關(guān)的可執(zhí)行文件。
在真實的程序開發(fā)過程中,不可能將所有的代碼寫在一個文件中,所以需要根據(jù)功能不同分塊寫入不同的.c文件中。但不同.c文件又需要調(diào)用其他文件中的函數(shù),如果直接include.c文件,那么在鏈接階段就會發(fā)現(xiàn)重復定義的錯誤。頭文件就是一個很好地解決方案,比如有A.c文件實現(xiàn)了一些函數(shù),可以在A.h中聲明這些函數(shù),當其他.c文件想使用A.c文件中的函數(shù)時,就可以includeA.h文件(頭文件名稱的對應只是一種規(guī)范,本質(zhì)上沒有關(guān)聯(lián)性)。
#include重復導入的問題:如果A.h和B.h都導入了C.h,那么當另外一個.c文件包含了A.h和B.h時,就會出現(xiàn)重復包含C.h的編譯錯誤。方法是將頭文件中的寫法改為
#ifndef C_H
#define C_H
//頭文件的真正內(nèi)容
#endif
Objective-C的#import
Objective-C是C語言的超集,支持面向?qū)ο蟆R虼司幾g過程可以類比C語言的編譯過程。編譯應該是以.m文件為單位,而.h文件作為接口暴露出來,其中寫類的聲明供其他文件調(diào)用。.m文件最終也是要進行鏈接組成可執(zhí)行文件。
注意,.h和.m本質(zhì)上同樣沒有對應關(guān)系,只有文件中聲明和實現(xiàn)的類是對應的。一個.m文件中可以寫多個類實現(xiàn),其所對應的類聲明也可以寫到不同的.h文件中。以類名命名相對應的.h和.m文件,并只寫該類的聲明與實現(xiàn)是一種規(guī)范。
在Objective-c語言中,使用#import來導入頭文件,其作用同樣是將頭文件內(nèi)容替換入該文件,只不過優(yōu)化的地方在于,使用#import指令,可以保證頭文件內(nèi)容不會重復導入。
關(guān)于如何優(yōu)化#import的編寫可以看這兩篇文章,#imports Gone Wild! How to Tame File Dependencies,Why #import Order Matters
基本思路就是
- 在.h頭文件中,盡量少的去引入其他的類或庫,多使用@class,@protocol這種前置聲明,然后在實現(xiàn)文件中真正用到類結(jié)構(gòu)時再#import,前置聲明可以最小化依賴關(guān)系,比如有A,B,C三個類,如果在B.h中導入了C.h,A.h中導入了B.h,那么當C.h中有修改,則A,B,C三個.m實現(xiàn)文件都需要重新編譯,但實際上A與C之間并不需要引用,所以此時在B.h中應該對C作前置聲明,在B.m中在導入,這樣當C.h改變時,只會影響真正使用到得B.m,節(jié)約了編譯時間
- 在.m實現(xiàn)文件中則要確保清除因各種原因遺留的已不需要的.h文件,另外,因為.m文件中導入的頭文件較多,還需要注意排序的問題。我一般習慣的順序是自身的頭文件,然后是項目內(nèi)的其他文件(順序是 controller、view、model、API請求類),接著是一些category的頭文件,最后是第三方庫的頭文件。感覺這樣的順序還是比較清晰的。
預編譯頭文件
.pch文件為precompiled prefix file,即預編譯頭文件。它的作用是對編譯過程加速,預編譯頭文件中導入的文件和其他一些內(nèi)容會被提前編譯,所以當項目真正編譯時,這些內(nèi)容可直接載入,不需要再去編譯了。在編譯階段,預編譯頭文件的內(nèi)容會被默認替換到每一個源文件的開頭,就相當于是XCode會幫你在文件開頭加這么一行
#import "xxx.pch"
所以其他文件可以直接使用在預編譯頭文件中的內(nèi)容而不需要導入這些頭文件了。基于這種情況我們可以將一些不會變化的框架的頭文件添加到該文件中,這些框架包含大量的文件可以被預先編譯,同時在使用這些公用的框架時也不需要再導入頭文件,就可以直接使用了。但是項目中實現(xiàn)的一些公用的類的頭文件并不應該放入預編譯頭文件中,因為預編譯頭文件的內(nèi)容會被引入到所有源文件中,所以一旦預編譯頭文件中導入的文件有改動,那么會導致項目中的其他文件都會被重新編譯(正常情況下,只會編譯一些改動過的和改動的時候影響到的文件)。而項目中的文件跟框架文件不同,即使是公用文件,也會存在一定頻率的改動。這樣做的另一個弊端是,源文件中會存在隱蔽的依賴關(guān)系,當該源文件被復用到另一個項目時,這些依賴關(guān)系并不能從導入的頭文件處看清楚,而是會看到一些報錯信息。
拋棄預編譯頭文件
從XCode6開始,新建的項目工程中已經(jīng)不再默認生成預編譯頭文件,這可能說明蘋果不建議再使用預編譯頭文件了,而原因則是另一個特性:Modules,Modules出現(xiàn)在更早的XCode5。
New Features in Xcode5
Modules for system frameworks speed build time and provide an alternate means to import APIs from the SDK instead of using the C preprocessor. Modules provide many of the build-time improvements of precompiled headers with less maintenance or need for optimisation. They are designed for easy adoption with little or no source changes. Beyond build-time improvements, modules provide a cleaner API model that enables many great features in the tools, such as Auto Linking.
All new projects created in Xcode 5 now build with modules enabled by default. For existing projects, you enable modules by using the project Build Settings panel. Search for “module” and set Enable Modules (C and Objective-C) to YES.
Auto Linking is enabled for frameworks imported by code modules. When a source file includes a header from a framework that supports modules, the compiler generates extra information in the object file to automatically link in that framework. The result is that, in most cases, you will not need to specify a separate list of the frameworks to link with your target when you use a framework API that supports modules.
Modules的作用就是加快框架的編譯速度并且提供一種代替預編譯的導入框架的方法。并且Modules支持Auto Linking,當你使用Modules時,編譯器會自動鏈接你所需要的框架而不需要你再去手動鏈接。Modules的語法時是@import,例如:
@import UIKit;
但其實原項目中的#import并不需要改動,如果支持Modules的話,Xcode會自動將其轉(zhuǎn)換為支持Modules的@import。
Modules相當于在編譯時載入了一個框架的已經(jīng)編譯的版本。其實質(zhì)是將框架進行了封裝,然后在實際編譯之時加入了一個用來存放已編譯添加過的Modules列表。如果在編譯的文件中引用到某個Modules的話,將首先在這個列表內(nèi)查找,找到的話說明已經(jīng)被加載過則直接使用已有的,如果沒有找到,則把引用的頭文件編譯后加入到這個表中。這樣被引用到的Modules只會被編譯一次,從而同時解決了編譯時間和引用泛濫兩方面的問題。
所以拋棄預編譯頭文件不會丟失加速編譯的特性,還可以避免其帶來的改動導致全局重新編譯和依賴顯示不明確的弊端。stackoverflow中對不使用預編譯頭文件的討論:
Why isn't ProjectName-Prefix.pch created automatically in Xcode 6?
另一篇介紹很全面的文章:
Modules and Precompiled Headers
最后
總結(jié)到此,我現(xiàn)在在項目中并沒用去掉預編譯頭文件,但我只保留了唯一的一個有關(guān)自定義Log的宏(實在不想在每一個源文件中添加一遍有關(guān)Log的頭文件了)。在新項目中,我想我會嘗試不使用預編譯頭文件,而是將有關(guān)Log的宏放在一個單獨的Log頭文件中,并且對系統(tǒng)框架的引用使用新的@import語法。最后提一句,現(xiàn)在Modules貌似已經(jīng)支持非系統(tǒng)框架了。