原文:http://zhenby.com/blog/2012/08/13/zai-jing-tai-ku-zhong-shi-yong-category/
問題
一個項目中使用了一個包含 category 的靜态庫,但是此項目在運作過程中,該靜态庫調用 category 增加的方法處,卻報 selector not recognized 異常。
最佳方案:方案四,趕時間的可以直接檢視方案四;
方案一
将 category 檔案跟靜态庫一起導入到工程中。
缺點
笨,而且多餘,在多個地方中存在同一份檔案,可能會帶來不一緻。
方案二
不使用 category,将 category 中新增加的方法增加一個參數,此參數就是 category 擴充的類的執行個體,例如要擴充 NSDictionary,要增加一個
(NSString *)JSONString
方法,那麼将此方法修改成
(NSString *)JSONStringWithDict: (NSDictionary *)dict
,也可以實作想要的效果。
缺點
如果是自己寫的 category ,修改起來還比較簡單 ,但是如果是開源項目中包含的 category,改動的工作量會很大;
需要額外的類,而且會導緻使用 category 的好處盡失。
問題原因
上面的兩個方案是在搞不清楚那個錯誤産生的原因時使用的兩個簡單、直接的方法,但是都太麻煩了。蘋果官方文檔中的這個 Q&A QA1490:Building Objective-C static libraries with categories 已經說明了這個問題産生的原因:
這個異常是因為标準 UNIX 靜态庫、linker 以及 Objective-C 的動态性三者之間的實作導緻的,Objective-C 不會為方法定義 linker symbols,它隻會為每一個類定義 linker symbols。如果你使用 category 擴充了一個已經存在的類,那麼 linker 不會将已有類的實作跟 category 的實作連接配接起來,這就導緻了調用靜态庫中 category 中新增加的方法時抛出 selector not recognized 的異常。
方案三
在使用靜态庫的 target 要将 -ObjC 選項傳遞給 linker,這個标志将會使得 linker 将靜态庫中原始類及 category 的類檔案都載入!
設定這個 -ObjC 選項的具體步驟
在 Xcode 中,檢視使用了靜态庫的那個 target 的 Building Settings,然後找到 Linking 類别中的 Other Linker Flags 選項,設定其值為 -ObjC ;
不過,設定 -ObjC 選項對于 iOS 程式來說有時是不夠的,這是因為 linker 中存在一個 bug,是以還是可能會在 -ObjC 的情況下導緻 selector not recognized 的異常,為了避免這個 bug,在 Other Linker Flags 中,我們将其值設定為 -all_load 或者 -force_load 即可,見下圖:

-all_load 與 -force_load 說明
- -all_load :linker 會将所有可見的檔案都載入到靜态庫中
- -force_load :從 Xcode3.2之後才有的選項,能使得檔案的載入更細化,每一個你要載入的檔案,都要增加一個 -force_load 選項,并且在 -force_load 後面跟上要導入的檔案路徑,例如:-force_load ../three20/Build/Products/Release-iphoneos/libThree20.a
缺點
- 使用 -all_load 會導緻很多多餘檔案的導入,會導緻靜态庫體積變大;
- 使用 -force_load 會很麻煩,要一個個手動添加。
方案四
facebook 的 three20 架構也遇到了這個問題,他們給出了一個更好的解決方案:
| |
上面的宏定義在 TTCorePreprocessorMacros.h 檔案中,在每個 category 的實作檔案開頭加上:TT_FIX_CATEGORY_BUG({cateory名字}) ,這樣就能避免在 iOS 中使用 -ObjC 的 linker 的 bug,但是記住,還是需要把使用靜态庫的 Target 中的 Building Setting 的 Other Linker Flags 設定成 -ObjC 。