如果使用得當(dāng)僵控,預(yù)編譯頭文件可以為您節(jié)省寶貴的編譯時(shí)間锉屈。但如果使用不當(dāng)贮懈,預(yù)編譯頭文件可能會(huì)隱藏源代碼中的問(wèn)題,而這些問(wèn)題可能會(huì)在你嘗試在另一個(gè)項(xiàng)目中重復(fù)使用部分源代碼時(shí)才被發(fā)現(xiàn)醇王。
本文是Objective-C 中的代碼氣味系列文章中的一篇。
預(yù)編譯頭文件的用途
發(fā)明預(yù)編譯頭文件的目的只有一個(gè):"加快編譯速度"崭添。與反復(fù)解析相同的頭文件相比厦画,這些文件只需提前解析一次。速度非常重要滥朱!編譯速度越快根暑,就能越快查看最近的更改是否成功,越快完成反饋循環(huán)徙邻。
在 Xcode 中排嫌,您可以將所需的頭文件包含在 "prefix header"中,并啟用 "Precompile Prefix Header"缰犁,從而對(duì)其進(jìn)行預(yù)編譯淳地。但前綴頭文件背后的理念與預(yù)編譯不同。前綴頭文件隱含在每個(gè)源文件的開頭帅容。例如颇象,如果你的前綴頭是 Prefix.pch
,那么每個(gè)源文件就會(huì)偷偷地
#import "Prefix.pch"
將其放在文件頂端并徘,比其他任何東西都先遣钳。這對(duì)于整個(gè)項(xiàng)目的 #defines
來(lái)說(shuō)很方便。(請(qǐng)記住麦乞,一般來(lái)說(shuō)蕴茴,#defines
是一種代碼氣味)。
對(duì)于預(yù)編譯頭文件來(lái)說(shuō)也很方便姐直。事實(shí)上倦淀,每個(gè)源文件都包含這些預(yù)編譯的頭文件,這也是前綴頭文件的一個(gè)特點(diǎn)声畏。
這就是事情開始出錯(cuò)的地方......
預(yù)編譯頭文件的存在并不是為了讓你省去打字的麻煩
Apple 的 iOS 項(xiàng)目模板以 Prefix.pch
開始撞叽,其中包括 Foundation
和 UIKit
。從編譯速度的角度來(lái)看插龄,這非常合理愿棋。問(wèn)題是,人們注意到了這一點(diǎn)辫狼,并說(shuō):"這些文件已經(jīng)隱含包含了初斑。所以我不需要再次包含它們"。發(fā)現(xiàn)這種副作用后膨处,一些程序員開始向 Prefix.pch
添加更多的頭文件见秤。因?yàn)檫@樣就不用再 #import
(導(dǎo)入)了砂竖。
目的從 "盡可能快地編譯這個(gè)項(xiàng)目 "轉(zhuǎn)變?yōu)?"節(jié)省自己的打字時(shí)間"。Stack Overflow 的一個(gè)問(wèn)題就反映了這一點(diǎn)鹃答,它問(wèn)道:"為什么有重復(fù)的#import乎澄?甚至維基百科的前綴詞條也反映了這一不正確的結(jié)論:"因此,沒有必要明確包含上述任何文件"测摔。這種誤解非常普遍置济。
這完全是錯(cuò)誤的。
過(guò)度依賴預(yù)編譯頭文件的四個(gè)問(wèn)題
問(wèn)題在于锋八,要成功編譯一個(gè)文件浙于,僅有成對(duì)的頭文件(.h)和實(shí)現(xiàn)文件(.m)已經(jīng)不夠了。你還需要 Prefix.pch
——不是因?yàn)樗鼈兪穷A(yù)編譯的挟纱,而是因?yàn)樗鼈兪请[式包含的羞酗。
"所以呢?"你問(wèn)紊服。"是什么阻礙了你檀轨?"基本上,你最終會(huì)創(chuàng)建不完整的源文件欺嗤。至少有四種方式會(huì)導(dǎo)致問(wèn)題:
1参萄、源文件無(wú)法復(fù)制到不同的項(xiàng)目中
假如你在前綴頭文件中添加了 <QuartzCore/QuartzCore.h>
。某個(gè)源文件使用了 QuartzCore
煎饼。試著將該源文件復(fù)制到另一個(gè)項(xiàng)目中讹挎。
很有可能無(wú)法編譯,因?yàn)榱硪粋€(gè)項(xiàng)目的預(yù)編譯頭文件不同腺占。你設(shè)法創(chuàng)建了一個(gè)不可移植的源文件淤袜!
2、依賴關(guān)系被隱藏
任何導(dǎo)入其他文件的系統(tǒng)都有一個(gè)好處衰伯,那就是可以顯示文件的依賴關(guān)系。你可以掃描 .h 或 .m 文件的開頭积蔚,看看它還使用了哪些其他文件意鲸。這可以讓你快速了解文件的范圍。
如果你的導(dǎo)入是隱式綁定在前綴頭文件中尽爆,情況就不一樣了怎顾。
3、依賴關(guān)系被掩藏
一個(gè)大型項(xiàng)目可能有大量的預(yù)編譯頭文件漱贱。假設(shè)你正在查看一個(gè)源文件槐雾,并試圖找到它的依賴關(guān)系。你很聰明地意識(shí)到幅狮,早期的程序員依賴預(yù)編譯頭文件來(lái)節(jié)省輸入募强,省略了許多 #import
株灸。所以你也查看了前綴文件。
但是擎值,如果 Prefix.pch
中的 #import
語(yǔ)句不只幾條慌烧,你的源文件需要哪些語(yǔ)句?全部鸠儿?不需要屹蚊?一些?哪些进每?
4汹粤、依賴關(guān)系失控
即使將所有 #imports
明確化,也很容易產(chǎn)生爆炸性的文件依賴關(guān)系田晚。讓依賴樹保持穩(wěn)定已經(jīng)很不容易了嘱兼。
但是,如果沒有努力做到:
a)使所有 #import
明確化肉瓦;
b)馴服它們遭京,
這些依賴關(guān)系就會(huì)悄無(wú)聲息地發(fā)展到不可收拾的地步。依賴關(guān)系的腐爛會(huì)在不知不覺中蔓延多年泞莉,直到為時(shí)已晚哪雕。突然間,你要開發(fā)一個(gè)新項(xiàng)目鲫趁,卻沒有一種簡(jiǎn)潔的方法來(lái)重用以前的代碼斯嚎,而又不會(huì)把它們都變成大量浪費(fèi)的垃圾。
查找并修復(fù)缺失的 #import
由于 Xcode 將前綴頭文件與預(yù)編譯頭文件結(jié)合在一起的方式挨厚,省略 #import
語(yǔ)句是一種常見的 Objective-C 代碼氣味堡僻。但這是一種不尋常的現(xiàn)象,因?yàn)檫@種氣味本身可能在很長(zhǎng)時(shí)間內(nèi)都不會(huì)被注意到疫剃。(無(wú)聲卻致命6ひ摺)。
要解決問(wèn)題巢价,就必須找到問(wèn)題所在牲阁。而要想找到問(wèn)題,就必須暫時(shí)移除阻嗅器:
- 編輯你的前綴文件壤躲。暫時(shí)注釋掉所有
#import
和#include
語(yǔ)句城菊。(譯者注,PS: 個(gè)人感覺對(duì)于一些明確的基類或者基礎(chǔ)的三方庫(kù)就別注釋了??)
2碉克、嘗試構(gòu)建您的項(xiàng)目凌唬。你會(huì)立刻發(fā)現(xiàn)問(wèn)題所在。
項(xiàng)目越大漏麦,做第一遍修復(fù)工作所需的時(shí)間就越長(zhǎng)客税。如果您覺得累了癞志,可以把它放在一邊揖闸,稍后再繼續(xù)清理否淤。但我還是希望你能把項(xiàng)目清理干凈审残。明確依賴關(guān)系是減少依賴關(guān)系的重要第一步。
譯自 Jon Reid 的 4 Ways Precompiled Headers Cripple Your Code
侵刪