版權(quán)聲明:本文源自簡書【九昍】晚树,歡迎轉(zhuǎn)載,轉(zhuǎn)載請務(wù)必注明出處: http://www.reibang.com/p/cb5e73adb6c3
編譯器把源文件(.c受葛,.cc题涨,.cpp偎谁,.m)轉(zhuǎn)化成對象文件(.o),源文件和對象文件是一一對應(yīng)的纲堵。對象文件(object file)里包含了符號巡雨、代碼和數(shù)據(jù),這種文件并不是直接被操作系統(tǒng)使用的席函。
當(dāng)你組建(build)動態(tài)庫(.dylib)铐望、framework、bundle(bundle)或者一個可執(zhí)行的二進(jìn)制文件的時候茂附,這些文件被鏈接器鏈接到一起來生成一些操作系統(tǒng)認(rèn)為“可用的”的東西正蛙,例如一些可以直接載入指定內(nèi)存地址的東西。
當(dāng)開發(fā)者組建(build)靜態(tài)庫(static library)的時候营曼,所有的對象文件被簡單的添加到一個大的歸檔文件里乒验,這就是.a(archive)文件的由來。所以.a文件就是一些對象文件的歸檔(想象一下沒有經(jīng)過壓縮的tar歸檔或者zip歸檔)蒂阱《腿拷貝單個.a文件要比拷貝一堆.o文件簡單的多(java也是一樣,為了使用方便你可以把一些.class文件放到一個.jar歸檔里)录煤。
在把二進(jìn)制鏈接到靜態(tài)庫(=archive)的時候鳄厌,編譯器會獲得一張含有歸檔里所有符號的表,然后編譯器會檢查哪些符號被這些二進(jìn)制文件引用了妈踊。只有包含被引用的符號的對象文件會被鏈接器真正的載入了嚎,并被鏈接進(jìn)程處理。例如廊营,如果你的文檔中有50個對象文件歪泳,但是只有20個包含被二進(jìn)制使用的符號,那么只有這20個文件會被鏈接器載入赘风,另外30個會被鏈接進(jìn)程完全忽略夹囚。
在C和C++代碼里,這種機(jī)制能很好地工作邀窃,因?yàn)檫@些語言會盡可能的在編譯期(C++也有一些runtime特性)去做這些事荸哟。然而Objective-C是一種與眾不同的語言,OC非常依賴runtime特性瞬捕,而且很多OC的特性都是只支持runtime的鞍历。OC類里面實(shí)際上也有類似于C函數(shù)或全局C變量的符號表(至少現(xiàn)在的OC runtime是這樣)。編譯器可以識別出一個類有沒有被引用肪虎,從而確定這個類是不是被使用了劣砍。如果你使用了靜態(tài)庫里的對象文件中的類,這個對象文件就會被鏈接器載入扇救,因?yàn)殒溄悠靼l(fā)現(xiàn)它的一個符號被使用了刑枝。
Category是runtime下特有的特性香嗓,它并不會像類或者函數(shù)一樣被符號化,也就是說装畅,編譯器并不能檢測到Category是不是被使用了靠娱。
如果鏈接器載入了一個包含OC代碼的對象文件,這些OC代碼的所有部分都是編譯階段的一部分掠兄。所以當(dāng)一個包含Category的對象文件因?yàn)槿魏畏柋徽J(rèn)為“在使用”(可能是一個類像云,可能是一個函數(shù),也可能是一個全局變量)蚂夕,它的Category也會被載入并在運(yùn)行期變得可用迅诬。基于前面的描述婿牍,一個只包含Cagegory的對象文件里沒有被編譯器認(rèn)為“在使用”的符號侈贷,所以是不會被載入的。
為了使在靜態(tài)庫中的類別能被使用牍汹,可以通過下面的5中方法解決:
通過在Other Linker Flags添加-all_load铐维,它會告訴編譯器“對于所有文檔中的所有對象文件柬泽,不管里面的符號有沒有被用到慎菲,全部都載入”,這種方法確實(shí)可以锨并,但是會產(chǎn)生比較大的二進(jìn)制文件露该。
另一種方法是添加-force_load和指定的路徑,這種方法和-all_load很像第煮,不同的是它只載入指定的歸檔解幼。
最受歡迎的方法是在Other Linker Flags中添加-ObjC,這個標(biāo)識告訴編譯器“如果你在文檔里的對象文件中發(fā)現(xiàn)了OC代碼包警,就把它載入“撵摆,Category里當(dāng)然也有OC代碼。使用這種方法不會載入任何沒有OC代碼的文件
另一種解決方法是新Xcode里build setting中的 Perform Single-Object PreLink害晦,如果啟用這個選項(xiàng)特铝,所有的對象文件都會被合并成一個單文件(這不是真正的鏈接,所以叫做預(yù)鏈接)壹瘟,這個對象文件(有時被稱做主對象文件(master object file))被添加到文檔中■杲耍現(xiàn)在如果主對象文件中的任何符號被認(rèn)為是“在使用”,整個主對象文件都會被認(rèn)為在使用稻轨,這樣它里面的OC部分就會被載入了灵莲。因?yàn)槔锩娴念惗急徽7柣耍阅苁箯倪@樣的靜態(tài)庫中使用所有的Category殴俱。
最后一種解決方法是在只有Category的源文件里添加Fake symbol政冻。如果你想在運(yùn)行時使用Category枚抵,一定要確保你以某種方法在編譯時引用了fake symbol,這會使得對象文件以及它里面的OC代碼被載入明场。例如俄精,它可以是一個有空函數(shù)體的函數(shù),也可以是一個被訪問的全局變量(例如一個全局的int變量榕堰,只要它被讀或者寫了一次就足夠了)竖慧。和上面其他的解決方法不一樣,這種解決方法可以控制哪些category可以在運(yùn)行時被編譯后的代碼使用(可以通過使用這個符號逆屡,使它們被鏈接并變得可用圾旨;也可以不使用這個符號,這樣鏈接器就會忽略它)魏蔗。
如果你有一個包含上百個對象的靜態(tài)庫砍的,但是你的二進(jìn)制文件只使用了其中的幾個,應(yīng)該避免使用1~4的方法莺治。否則會得到一個非常大的包含了所有類(可能這些類根本沒被用到)的二進(jìn)制文件廓鞠。對于一個Class,通常不用做特殊的處理谣旁;而對于一個Category床佳,考慮一下e中解決方案,它可以讓你只包含想要的Category榄审。
例如:如果想使用NSData的類別砌们,添加兩個方法compressionData和decompressionData,可以創(chuàng)建一個這樣的頭文件搁进。
// NSData+Compress.h
@interface NSData (Compression)
- (NSData *)compressedData;
- (NSData *)decompressedData;
@end
void import_NSData_Compression ( ); //注意這個方法*************
再添加一個這樣的實(shí)現(xiàn)文件
// NSData+Compress
@implementation NSData (Compression)
- (NSData *)compressedData
{
// ... magic ...
}
- (NSData *)decompressedData
{
// ... magic ...
}
@end
void import_NSData_Compression ( ) { } //注意這里***************
然后確保在代碼里調(diào)用了import_NSData_Compression這個方法浪感。是不是真的調(diào)用或者調(diào)用的次數(shù)并不重要,實(shí)際上并不需要真的去調(diào)用它饼问,只需要讓編譯器認(rèn)為你調(diào)用了這個方法就行了影兽,比如可以把這段代碼添加到工程的任何地方
__attribute__((used)) static void importCategories ()
{
import_NSData_Compression();
// add more import calls here
}
這樣寫以后并不需要在代碼里調(diào)用importCategories (),attribute將會讓編譯器認(rèn)為它被調(diào)用了莱革。
如果添加了-whyload標(biāo)識峻堰,鏈接器會打印組件日志告訴你“哪個二進(jìn)制文件里的哪個對象文件因?yàn)槟囊粋€符號被使用所以被載入了”。但是它只會打印第一個被認(rèn)為是“使用中”的符號驮吱。
-dead strip對OC不適用盔粹,不感興趣的可以不往下看了
編譯器還有一個屬性叫做 –dead_strip幻赚,如果編譯器決定再入一個對象文件,這個文件里的所有符號都會變成鏈接后的二進(jìn)制文件中的一部分,不管它們有沒有被使用衅斩。比如有一個對象文件包含了100個函數(shù)船老,但是它們中只有一個被二進(jìn)制文件使用了,所有的100個函數(shù)仍然會被添加到二進(jìn)制文件里,因?yàn)閷ο笪募荒鼙煌暾奶砑踊蛘卟槐惶砑诱纭f溄悠鞑恢С植糠痔砑訉ο笪募?/p>
但是,如果告訴鏈接器“dead strip”牧氮, 鏈接器首先會把所有的對象文件添加到二進(jìn)制文件琼腔,解決所有的引用,然后掃描二進(jìn)制文件中的符號是不是在被使用(或者是被一些沒有被使用的符號使用)踱葛。所有的被找到的沒有被使用的符號都會在優(yōu)化階段被移除丹莲。在上面的例子里,99個沒有被使用的函數(shù)會被移除尸诽。如果你使用了-load_all,-force_load或者Perform Single-Object PreLink甥材,這個標(biāo)識會非常有用,因?yàn)檫@三個選項(xiàng)在一些情況下很容易增加二進(jìn)制文件的大小性含,而dead strip將會移除那些沒用的代碼和數(shù)據(jù)洲赵。
Dead strip對于C代碼能很好的工作(例如:像預(yù)期的那樣去掉沒用的函數(shù)、變量和常量)商蕴,它在C++上也能工作的不錯(例如:沒用的類能夠被移除)叠萍。雖然它并不完美,在一些情況下一些符號沒有被移除绪商,但是在大多數(shù)情況下它能在這些語言下很好地工作苛谷。
在OC
OC里面,并不支持dead strp部宿,因?yàn)镺O是一種具有runtime特性的語言抄腔,編譯器并不能在編譯時確定符號是不是真的被使用了。例如理张,如果沒有代碼直接使用一個OC的類,那他就是沒有被使用嗎绵患?當(dāng)然不是雾叭,開發(fā)者可以動態(tài)創(chuàng)建一個包含類名的字符串,獲得這個類的一個指針落蝙,并動態(tài)的執(zhí)行alloc织狐。
比如:
MyCoolClass * mcc = [[MyCoolClass alloc] init];
也可以寫成
NSString * cname = @"CoolClass";
NSString * cnameFull = [NSString stringWithFormat:@"My%@", cname];
Class mmcClass = NSClassFromString(cnameFull);
id mmc = [[mmcClass alloc] init];
第二種寫法并沒有直接引用這個類,對象的創(chuàng)建通過runtime完成筏勒。
原文鏈接http://stackoverflow.com/questions/2567498/objective-c-categories-in-static-library
感謝作者 Mecki