Targets选项下有Other linker flags的设置,用来填写XCode的链接器参数,如:-ObjC -all_load -force_load等。
还记得我们在学习C程序的时候,从C代码到可执行文件经历的步骤是:
源代码 > 预处理器 > 编译器 > 汇编器 > 机器码 > 链接器 > 可执行文件
在 最后一步需要把.o文件和C语言运行库链接起来,这时候需要用到ld命令。源文件经过一系列处理以后,会生成对应的.obj文件,然后一个项目必然会有许 多.obj文件,并且这些文件之间会有各种各样的联系,例如函数调用。链接器做的事就是把这些目标文件和所用的一些库链接在一起形成一个完整的可执行文 件。
如果要详细研究链接器做了什么,请看:http://www.dutor.net/index.php/2012/02/what-linkers-do/
那么,Other linker flags设置的值实际上就是ld命令执行时后面所加的参数。
下面逐个介绍3个常用参数:
-ObjC:加了这个参数后,链接器就会把静态库中所有的Objective-C类和分类都加载到最后的可执行文件中
-all_load: 会让链接器把所有找到的目标文件都加载到可执行文件中,但是千万不要随便使用这个参数!假如你使用了不止一个静态库文件,然后又使用了这个参数,那么你很 有可能会遇到ld: duplicate symbol错误,因为不同的库文件里面可能会有相同的目标文件,所以建议在遇到-ObjC失效的情况下使用-force_load参数。
-force_load:所做的事情跟-all_load其实是一样的,但是-force_load需要指定要进行全部加载的库文件的路径,这样的话,你就只是完全加载了一个库文件,不影响其余库文件的按需加载
加入-ObjC链接参数后项目走火入魔的事件由来以久,往日只曾听说,却从未见过。最近门派开发产品的时候,本人经身经历了一番。
这个坑是怎么来的?
公司项目遇到的情况如下:首先我们公司的项目是用CocoaPod构建的,CocoaPod在生成项目的时候会自动在Other Link Flags配置项上打上-ObjC,而且,即使强行去除-ObjC选项也无法解决,虽然编译可通过,但是运行的时候,友盟、MJRefresh等众多库均会报错无法运行。
同时,公司的项目基于另一个分公司所提供的一个基础服务framework,这个framework具体实现未知,目前看来应该是使用c++开发,同时库必须禁用掉-ObjC选项,否则会报 duplicated symbols错误,编译都无法通过。
于是坑就这样出来了。
-ObjC是干嘛的?
简单来说,-ObjC链接指令是用来解决static library在运行时调用category方法报selector not recognized错误时使用的。也就是说,如果你在一个static library里面声明了一个category,在运行的时候调用这个方法就很有可能会出现这个错误,而这个错误本不应该出现,因为你已经定义了那个方法。
那么,为什么会出现这样一个问题?
为啥会出 method not recognized 错误
简单来说,这是因为UNIX 的静态库(.a文件)与OC的动态机制之间的不协调导致的。
我们先来看一般情况下,UNIX静态库及C程序的一个链接过程。当一个C语言程序编译的时候,所有的源代码会被编译为对象文件,即.o文件(object file)。这些对象文件中包含了相应的可执行程序,以及相应的静态数据。链接器最终需要将所有这些对象文件组合到一起从而产生一个最终的可执行文件。
当一个源文件引用了定义于其他文件中的一些东西的时候(比如引用其他文件的一个方法),一个 undefined symbol就被写入了它所产生的object 文件,然后等待最终被解释掉。在最终构建可执行文件的时候,链接器将从包含这些undefined symbols的object文件中拉取信息以解决掉之前被标记的undefined symbols。
一个UNIX的静态库其实就是一系统object file的集合,然而,一般情况下只会拉取那些,他需要的object file。而这样做的好处是可以减小最终可执行文件的大小。
举个例子
比如main.c使用一个函数,名叫foo( ),而这个函数定义于B.c里面。在生成.o文件的时候,main.o就会有一个foo( ) 的undefined symbols标记。在链接期间,B.o文件便会被打入最终的可执行文件中。但是,假如另外还有一个C.c,里面定义了的函数并没被使用到,那么最终,这个C.o文件便不会出现在最终的可执行文件中。
Objective-C有什么不同?
然而众所周之,oc是具有一定动态性的语言,只有在最终运行的时候,对象方法的具体实现只有到被调用的时候才会被确定。基于这个原因,Objective-C并没有对方法级别定义符号,而是只对类级别定义符号。
举个栗子。比如在main.c中包含以下代码: [[FooClass alloc] initWithBar:nil]; 那么在链接的时候,main.o在生成的时候就会包含一个未定义符号(Undefined Symbols) FooClass,但却不会定义符号 initWithBar。
所以为什么会产生这个坑?
坑爹的是,Category只是一个方法的集合,而对一个Cateogry中的方法的调用,并不会生成未定义符号,这就意味着链接器并不知道要去加载这个Cateogry文件所生成的object 文件。于是,在最后运行的时候,运行时系统便会无法找到相应方法的定义,从而抛出unrecognized selector 错误。
-ObjC干了啥?
那为啥加上-ObjC就好了? 原来,加上-ObjC选项的时候,链接器便会加载静态Library里面所有Objective-C实现的类和Cateogry。
还有啥选项?
除了-ObjC,文档上还有几个其他的选项,也值得我们关注一下。
-all_load 全加载,意思是加载所有静态库的成员。无论是c还是c++还oc。
-force_load path_to_load 对某个指定静态库全加载。而-all_load则会对所有的库进行全加载。
这到这里,这个问题貌似已经可以解决了。嗯,分公司的开发肯定在framework里面打包了几个没有用到的额外同名实现,而加上-ObjC,会将所有方法全加载上,于是duplicated symbols错误便被抛出。
那么,如果libPod,也就是pod库生成的静态库前面加上-force_load,而不加上-ObjC,问题不就顺利解决了?