重拾iOS-import

關(guān)鍵詞:#import,#include,@class,Modules,預處理(preprocessor)

一、概述

#include是C/C++導入頭文件的關(guān)鍵字;
#import是Objective-C導入頭文件關(guān)鍵字;
@class告訴編譯器某個類的聲明,當執(zhí)行時,才去查看類的實現(xiàn)文件,可以解決頭文件的相互包含的問題;

二、#import"xxx.h" 和 #import<xxx/xxx.h> 的區(qū)別

1)區(qū)別

#import<xxx/xxx.h>: 引用系統(tǒng)文件,它用于對系統(tǒng)自帶的頭文件的引用,編譯器會在系統(tǒng)文件目錄下去查找該文件。

#import"xxx.h": 用戶自定義的文件用雙引號引用,編譯器首先會在用戶目錄下查找,然后到安裝目錄中查。

雙引號是用于本地的頭文件,需要指定相對路徑,尖括號是全局的引用,其路徑由編譯器提供,如引用系統(tǒng)的庫。

2)use header map

Enable the use of Header Maps, which provide the compiler with a mapping from textual header names to their locations, bypassing the normal compiler header search path mechanisms. This allows source code to include headers from various locations in the file system without needing to update the header search path build settings。

意思是Xcode開啟這個開關(guān)后,在本地會根據(jù)當前目錄生成一份文件名和相對路徑的映射,依靠這個映射,我們可以直接import工程里的文件,不需要依靠header search path。

如果關(guān)閉use header map那么就涉及到xcode build settings中的header search pathuser header search path了。

兩者都是提供search path的,區(qū)別在于一個指明是用戶的。并且提到如果編譯器不支持user headers概念,會從header search paths中去尋找。


三、存在的問題

傳統(tǒng)的#include/#import都是文本語義: 預處理器在處理的時候會把這一行替換成對應頭文件的文本。

這樣就會導致以下問題:

問題1)大量的預處理消耗;

假如有N個頭文件,每個頭文件又#include了M個頭文件,那么整個預處理的消耗是N*M。這可能會在你的頭文件里面引入數(shù)量非常龐大的代碼。假如換成 C++,那情況就更糟了,因為它還包含了一些模板代碼,數(shù)量比 C 還要多好幾倍;

問題2)文件導入后,宏定義容易出現(xiàn)問題;

因為是文本導入,并且按照include依次替換,當一個頭文件定義了#define std hello_world,而第另一個頭文件剛好又是C++標準庫,那么include順序不同,可能會導致所有的std都會被替換。那最終運行時結(jié)果就出乎意料了,這使得預處理變得非?!按嗳酢保?/p>

問題3)邊界不明顯;

什么時候用什么工具、庫來開發(fā)軟件,僅僅從頭文件上面看其實你并不能看得懂,因為它并不是“語義化”的,比如哪些命名空間屬于特定的庫,比如拿到一組.a和.h文件,很難確定.h是屬于哪個.a的?這些命名空間又該以如何的順序包含,需要以什么樣的順序?qū)氩拍苷_編譯?或者你又只想引入這個庫的一部分定義,只通過 “#include”之類的預處理真的是太難搞清楚了;

問題4)引用泛濫;

A 類導入了 C 類的頭文件,B 類也導入了 C 類的頭文件,D 類又同時導入 A 和 B 類,這就是重復導入;

問題5)交叉引用;

如果有循環(huán)依賴關(guān)系,如:A–>B, B–>A這樣的相互依賴關(guān)系。如:



當在TestA類的.h中 #import "TestB.h" 并聲明TestB屬性時報錯:
Unknown type name 'TestB'; did you mean 'TestA'?

解決辦法:在.h文件中使用@class,然后在.m文件中#import "TestB.h"即可。

原因: @class TestB;這句話的意思就是,告訴編譯器,確實有TestB這個類,具體細節(jié)你不用管,別報錯就行了。所以顯然,到了.m里,它只知道有這個類,卻不知道這個類有什么屬性,有哪些方法。所以需要在.m再 import這個頭文件。

【注意】在.m文件中#import "TestB.h" 并聲明TestB屬性是不會報錯的;

盡量在.m而不是.h里使用import引用

推薦盡量在.m里引用頭文件,而不是在.h里,必要時使用@class。

在編譯效率方面考慮,如果你有100個頭文件都#import了同一個頭文件,或者這些文件是依次引用的,如A–>B, B–>C, C–>D這樣的引用關(guān)系。當最開始的那個頭文件有變化的話,后面所有引用它的類都需要重新編譯,如果你的類有很多的話,這將耗費大量的時間。而是用@class則不會。

但是也有一些情況,是不可避免要在.h里引用的。比如:繼承某個類,必須在.h里 import 父類的.h;類實現(xiàn)某個接口,必須在.h里引用接口的.h等等。


四、Clang Module

Modules 是一種語義化的引入定義的方法。

clang module不再使用文本模型, 而是采用更高效的語義模型。clang module提供了一種新的導入方式:@import,module會被作為一個獨立的模塊編譯,并且產(chǎn)生獨立的緩存,從而大幅度提高預處理效率,這樣時間消耗從M*N變成了M+N。

// Swift
@import WebKit.WebKitLegacy; //in Objective-C
import WebKit.WebKitLegacy   //in Swift
// OC
#import "TestA.h"
#import <WebKit/WebKit.h>

可以看到 Objective-C 和 Swift 都非常好地支持了 Modules import,你可以非常清晰地引入 API 聲明。

當你使用 Modules 引入時,預處理器并不會像 “#include”那樣使用 M*N 量級的重復拷貝粘貼。而是巧妙地通過一個列表來存放已經(jīng)編譯處理過的 Modules 列表,而聲明的引入會首先在這個表內(nèi)查找,如果沒有找到會去編譯添加進來。所以 Modules 的引入只會被處理一次,可以解決前面提到的引用泛濫問題。

自 Xcode5以來,build settings 都默認開啟了 “-fmodules”,一般來講你的代碼里面都可以使用 Modules 來引入其他庫。其實 modules 是一種頭文件編譯后的 map,所以 Modules 始終都能保證你所引入的定義是存在的、有意義的。(其實 Modules 是一種從 precompile headers 演變過來的技術(shù))

modules 和 headers 通過一個 map 來進行一種關(guān)系映射,這個 map 文件就叫做 modulemap. 這個文件從語義上描述了你的函數(shù)庫物理結(jié)構(gòu)。

舉個例子,用 std 這個 module 來描述 C 的標準庫。那么 C 標準庫里面的那些頭文件:stdio.h, stdlib.h, math.h 都可以映射到 std 這個 module 里面,他們就組成了幾個 子模塊(submodule): std.io, std.lib, std.math。通過這樣一個映射關(guān)系,C 的標準庫就可以構(gòu)建出一個獨立的 module。所以通常地,一個庫就只有一個 module.modulemap 文件用于描述它的所有頭文件映射。

那么實際在編譯過程中 Modules 到底代表著什么呢?我們前面說過其實 Modules 是一種預編譯技術(shù),當一個模塊被導入時,編譯器在處理它時會生成一個新的子進程(非 fork),這個子進程擁有干凈的 context來編譯這個 module(這樣就不會產(chǎn)生命名空間沖突等干擾),然后 module 的編譯結(jié)果會被持久化到這個模塊的二進制緩存中,那么下次引用編譯的時候就會非??臁?modules 由頭文件映射而成,所以當這些頭文件改動時,module 還會自動重新編譯刷新緩存,不需要我們主動干預。


相關(guān)參考

1)LLVM的 Modules https://www.stephenw.cc/2017/08/23/llvm-modules/

2)關(guān)于Objective-C中的import https://juejin.im/post/58d88c7ab123db199f442aec

3)相互引用頭文件問題 https://juejin.im/post/5aaf6943518825556e5de48e

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

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

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