Wednesday, December 14, 2011

解決static library(.a)在IB以及某些category的靈異狀況。

Interface Builder是個很神奇的東西,裡面很多神秘的機制。其中最為讓人難以裡解的就是他跟dependency的恩怨情仇。對,android的java有更嚴重的問題(在UI上,android甚至沒辦法access到dependency的resource),不過這個Bug完全是因為Interface的linking機制是等到obj編譯完以後才開始找的關係(所以這個bug基本上只會出現在自定的widget class)。

這個bug大概會以這種形式發作:我們的.a(static靜態函示庫)裡面有某個widget class,而引用端的Interface Builder會引用這個class, 於是在某種情況下就出錯了

2011-12-14 13:58:50.999 TestPlatform[294:707] Unknown class MyClassBtn in Interface Builder file.

2011-12-14 13:58:51.010 TestPlatform[294:707] Unknown class MyClass2Btn in Interface Builder file.


這出錯的原因很簡單。因為Interface Builder是從.obj(compile from .a)裡面去獲得class的名字以及資訊,但是他找不到(廢話)。為什麼找不到?因為.obj裡面真的沒有這個symbol。所以IB只好很哀怨地把他設定回預設的class,比方說UIButton。當然,當你真的invoke起這個button又用了只有MyClassBtn才有的method,他就會理所當然地吐一個unregconized selector的runtime exception給你。

所以問題出在linker,自作主張地把這個.a裡面的symbol沖倒馬桶去沒有編譯進去.obj了。linker在編譯.a編譯成.obj的時候,為了一些效能以及檔案大小上的理由,會主動地去尋找其他的.obj裡面到底有沒有真的用到某個symbol,要是他在其他obj裡面找不到,那自然就會理所當然地把它扔了。

然後IB自然就沒辦法在編譯出來的.a找到symbol,一切合情合理。

解決的方法有兩個,我先聊聊第一個比較簡單的方法。為了防止linker愚蠢的(這樣將不太公平,天曉得這東西IB會用到?)把不該丟掉的symbol給沖到馬桶去,所以我們必須要呼叫他的static method來欺騙linker,這東西是有用的,不准丟掉。link show了一個比較hack的方法,你做一個static method,但是什麼都不做,然後在host呼叫他,linker就會被欺騙而不會把.a裡面的symbol丟掉才編成.obj。

其實下面也提到一個更簡單的方法,事實上你呼叫class靜態方法也可以,比方說[MyClassBtn class]。

後面會提到第二種方法,同樣可以解決這問題。不過再看第二種方法以前,我們先看看另外一個靈異現象。

Category也有類似的問題,不過起因不太一樣。當你去Categorize一個Native class(最常見的,恩,NSString)在編譯的時候會被錯誤的當作沒用到的丟棄了--這起因應該主要是因為linker的objc旗標buggy,我猜的--所以同樣的在使用static library時,library裡面native class的category都會無作用(很妙的是,自動完成使用的資料庫是.h,所以自動完成會自動地幫你找到method,但是runtime會因為找不到這個selector炸掉,真悲)。

解決的方法就是前面提過的第二種方法 -- 強制linker「你什麼symbol都不准丟掉」。在宿主的build setting裡面,找到other linker flags,加上-all_load或者-force_load(老實講前後兩者不太一樣,但是我沒有去追究他們到底有什麼細微的不同)。這同樣也可以解決IB找不到symbol的問題。

所以我們可以替這兩種方法做一個總結。第一種方法是藉由對linker額外的提示(但是code會混亂不堪),明確地告訴linker那些東西要額外地放進來。所以.a->.obj並不會include過多的symbol,不會造成.obj又肥又大的問題。當然缺點就是code要用一些非常莫名的東西來提示linker--當然IBOutlet/IBAction都用了,不在乎再多個幾行莫名其妙的code。我們其實可以藉由#define來稍為美化他一下,至少讓他[MyClass class];這行看起來不會那麼奇怪。另外,他對於category造成的問題其實是無解的。

第二種方法則沒有以上的所有缺點,但是會造成.a->.obj非常大--尤其當你的.a包山包海無所不包的時候(比方說我個人非常愛的Utility classes)--然後大通常伴隨的就是慢。不過,在普通的輕量級class裡面,這算是一個可以接受的效能損失。當然,category造成的問題,這是唯一解的樣子。聽說-force_load效能會比-all_load效能好,不過我沒比過。

static library問題多,還是用source code編譯最好(遠目)