使用一个包含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 即可,见下图:
设置 -ObjC

-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 框架也遇到了这个问题,他们给出了一个更好的解决方案:

/**
* Add this macro before each category implementation, so we don't have to use
* -all_load or -force_load to load object files from static libraries that only contain
* categories and no classes.
* See http://developer.apple.com/library/mac/#qa/qa2006/qa1490.html for more info.
*/

#define TT_FIX_CATEGORY_BUG(name) @interface TT_FIX_CATEGORY_BUG_##name @end \
@implementation TT_FIX_CATEGORY_BUG_##name @end

上面的宏定义在 TTCorePreprocessorMacros.h 文件中,在每个 category 的实现文件开头加上:TT_FIX_CATEGORY_BUG({cateory名字}) ,这样就能避免在 iOS 中使用 -ObjC 的 linker 的 bug,但是记住,还是需要把使用静态库的 Target 中的 Building Setting 的 Other Linker Flags 设置成 -ObjC 。