重拾iOS-import

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

一氓拼、概述

#include是C/C++導(dǎo)入頭文件的關(guān)鍵字寞秃;
#import是Objective-C導(dǎo)入頭文件關(guān)鍵字;
@class告訴編譯器某個(gè)類(lèi)的聲明世蔗,當(dāng)執(zhí)行時(shí)嚎幸,才去查看類(lèi)的實(shí)現(xiàn)文件,可以解決頭文件的相互包含的問(wèn)題洗贰;

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

1)區(qū)別

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

#import"xxx.h": 用戶(hù)自定義的文件用雙引號(hào)引用,編譯器首先會(huì)在用戶(hù)目錄下查找绎晃,然后到安裝目錄中查蜜唾。

雙引號(hào)是用于本地的頭文件,需要指定相對(duì)路徑庶艾,尖括號(hào)是全局的引用袁余,其路徑由編譯器提供,如引用系統(tǒng)的庫(kù)咱揍。

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開(kāi)啟這個(gè)開(kāi)關(guān)后,在本地會(huì)根據(jù)當(dāng)前目錄生成一份文件名和相對(duì)路徑的映射碗淌,依靠這個(gè)映射,我們可以直接import工程里的文件噪漾,不需要依靠header search path。

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

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


三、存在的問(wèn)題

傳統(tǒng)的#include/#import都是文本語(yǔ)義: 預(yù)處理器在處理的時(shí)候會(huì)把這一行替換成對(duì)應(yīng)頭文件的文本存淫。

這樣就會(huì)導(dǎo)致以下問(wèn)題:

問(wèn)題1)大量的預(yù)處理消耗耘斩;

假如有N個(gè)頭文件,每個(gè)頭文件又#include了M個(gè)頭文件桅咆,那么整個(gè)預(yù)處理的消耗是N*M括授。這可能會(huì)在你的頭文件里面引入數(shù)量非常龐大的代碼。假如換成 C++岩饼,那情況就更糟了荚虚,因?yàn)樗€包含了一些模板代碼,數(shù)量比 C 還要多好幾倍籍茧;

問(wèn)題2)文件導(dǎo)入后版述,宏定義容易出現(xiàn)問(wèn)題;

因?yàn)槭俏谋緦?dǎo)入寞冯,并且按照include依次替換渴析,當(dāng)一個(gè)頭文件定義了#define std hello_world,而第另一個(gè)頭文件剛好又是C++標(biāo)準(zhǔn)庫(kù)吮龄,那么include順序不同俭茧,可能會(huì)導(dǎo)致所有的std都會(huì)被替換。那最終運(yùn)行時(shí)結(jié)果就出乎意料了漓帚,這使得預(yù)處理變得非衬刚“脆弱”;

問(wèn)題3)邊界不明顯尝抖;

什么時(shí)候用什么工具毡们、庫(kù)來(lái)開(kāi)發(fā)軟件,僅僅從頭文件上面看其實(shí)你并不能看得懂昧辽,因?yàn)樗⒉皇恰罢Z(yǔ)義化”的漏隐,比如哪些命名空間屬于特定的庫(kù),比如拿到一組.a和.h文件奴迅,很難確定.h是屬于哪個(gè).a的青责?這些命名空間又該以如何的順序包含挺据,需要以什么樣的順序?qū)氩拍苷_編譯?或者你又只想引入這個(gè)庫(kù)的一部分定義脖隶,只通過(guò) “#include”之類(lèi)的預(yù)處理真的是太難搞清楚了扁耐;

問(wèn)題4)引用泛濫;

A 類(lèi)導(dǎo)入了 C 類(lèi)的頭文件产阱,B 類(lèi)也導(dǎo)入了 C 類(lèi)的頭文件婉称,D 類(lèi)又同時(shí)導(dǎo)入 A 和 B 類(lèi),這就是重復(fù)導(dǎo)入;

問(wèn)題5)交叉引用构蹬;

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



當(dāng)在TestA類(lèi)的.h中 #import "TestB.h" 并聲明TestB屬性時(shí)報(bào)錯(cuò):
Unknown type name 'TestB'; did you mean 'TestA'?

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

原因: @class TestB;這句話(huà)的意思就是,告訴編譯器藻烤,確實(shí)有TestB這個(gè)類(lèi)绷雏,具體細(xì)節(jié)你不用管,別報(bào)錯(cuò)就行了怖亭。所以顯然涎显,到了.m里,它只知道有這個(gè)類(lèi)兴猩,卻不知道這個(gè)類(lèi)有什么屬性期吓,有哪些方法。所以需要在.m再 import這個(gè)頭文件倾芝。

【注意】在.m文件中#import "TestB.h" 并聲明TestB屬性是不會(huì)報(bào)錯(cuò)的讨勤;

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

推薦盡量在.m里引用頭文件,而不是在.h里蛀醉,必要時(shí)使用@class

在編譯效率方面考慮衅码,如果你有100個(gè)頭文件都#import了同一個(gè)頭文件拯刁,或者這些文件是依次引用的,如A–>B, B–>C, C–>D這樣的引用關(guān)系逝段。當(dāng)最開(kāi)始的那個(gè)頭文件有變化的話(huà)垛玻,后面所有引用它的類(lèi)都需要重新編譯,如果你的類(lèi)有很多的話(huà)奶躯,這將耗費(fèi)大量的時(shí)間帚桩。而是用@class則不會(huì)。

但是也有一些情況嘹黔,是不可避免要在.h里引用的账嚎。比如:繼承某個(gè)類(lèi)莫瞬,必須在.h里 import 父類(lèi)的.h;類(lèi)實(shí)現(xiàn)某個(gè)接口郭蕉,必須在.h里引用接口的.h等等疼邀。


四、Clang Module

Modules 是一種語(yǔ)義化的引入定義的方法召锈。

clang module不再使用文本模型, 而是采用更高效的語(yǔ)義模型旁振。clang module提供了一種新的導(dǎo)入方式:@import,module會(huì)被作為一個(gè)獨(dú)立的模塊編譯涨岁,并且產(chǎn)生獨(dú)立的緩存拐袜,從而大幅度提高預(yù)處理效率,這樣時(shí)間消耗從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 聲明。

當(dāng)你使用 Modules 引入時(shí)沮尿,預(yù)處理器并不會(huì)像 “#include”那樣使用 M*N 量級(jí)的重復(fù)拷貝粘貼丛塌。而是巧妙地通過(guò)一個(gè)列表來(lái)存放已經(jīng)編譯處理過(guò)的 Modules 列表,而聲明的引入會(huì)首先在這個(gè)表內(nèi)查找畜疾,如果沒(méi)有找到會(huì)去編譯添加進(jìn)來(lái)赴邻。所以 Modules 的引入只會(huì)被處理一次,可以解決前面提到的引用泛濫問(wèn)題啡捶。

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

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

舉個(gè)例子勿她,用 std 這個(gè) module 來(lái)描述 C 的標(biāo)準(zhǔn)庫(kù)袄秩。那么 C 標(biāo)準(zhǔn)庫(kù)里面的那些頭文件:stdio.h, stdlib.h, math.h 都可以映射到 std 這個(gè) module 里面,他們就組成了幾個(gè) 子模塊(submodule): std.io, std.lib, std.math逢并。通過(guò)這樣一個(gè)映射關(guān)系之剧,C 的標(biāo)準(zhǔn)庫(kù)就可以構(gòu)建出一個(gè)獨(dú)立的 module。所以通常地砍聊,一個(gè)庫(kù)就只有一個(gè) module.modulemap 文件用于描述它的所有頭文件映射背稼。

那么實(shí)際在編譯過(guò)程中 Modules 到底代表著什么呢?我們前面說(shuō)過(guò)其實(shí) Modules 是一種預(yù)編譯技術(shù)玻蝌,當(dāng)一個(gè)模塊被導(dǎo)入時(shí)蟹肘,編譯器在處理它時(shí)會(huì)生成一個(gè)新的子進(jìn)程(非 fork)词疼,這個(gè)子進(jìn)程擁有干凈的 context來(lái)編譯這個(gè) module(這樣就不會(huì)產(chǎn)生命名空間沖突等干擾),然后 module 的編譯結(jié)果會(huì)被持久化到這個(gè)模塊的二進(jìn)制緩存中疆前,那么下次引用編譯的時(shí)候就會(huì)非澈快。 modules 由頭文件映射而成竹椒,所以當(dāng)這些頭文件改動(dòng)時(shí)童太,module 還會(huì)自動(dòng)重新編譯刷新緩存,不需要我們主動(dòng)干預(yù)胸完。


相關(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)相互引用頭文件問(wèn)題 https://juejin.im/post/5aaf6943518825556e5de48e

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末书释,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子赊窥,更是在濱河造成了極大的恐慌爆惧,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锨能,死亡現(xiàn)場(chǎng)離奇詭異扯再,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)址遇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)熄阻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人倔约,你說(shuō)我怎么就攤上這事秃殉。” “怎么了浸剩?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵钾军,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我绢要,道長(zhǎng)吏恭,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任重罪,我火速辦了婚禮樱哼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蛆封。我一直安慰自己唇礁,他們只是感情好勾栗,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布惨篱。 她就那樣靜靜地躺著,像睡著了一般围俘。 火紅的嫁衣襯著肌膚如雪砸讳。 梳的紋絲不亂的頭發(fā)上琢融,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音漾抬,去河邊找鬼纳令。 笑死,一個(gè)胖子當(dāng)著我的面吹牛捏雌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肤频,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼米同,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼面粮!你這毒婦竟也來(lái)了稍走?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鸿脓,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年喘垂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了正勒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祥绞。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡败京,死狀恐怖朴皆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布脆荷,位于F島的核電站,受9級(jí)特大地震影響剑肯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜观堂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一让网、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧师痕,春花似錦溃睹、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至笔横,卻和暖如春竞滓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背吹缔。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工虽界, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人涛菠。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓莉御,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親俗冻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子礁叔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容