您的位置:首页 > 移动开发 > IOS开发

关于-Objc -all_load -force_load的流言终结贴

2017-01-18 20:17 267 查看
公司的一个IOS项目发生了duplicate symbol的编译错误,去掉-all_load之后可以编译通过,但是运行的时候其中一段代码报错了,大家肯定能想到是什么问题,‘unrecognized selector’,具体问题我将稍后详细分析。

 
从技术层面来讲,其实就是如何正确使用-Objc -all_load -force_load的问题,不过我发现网上很多帖子说的都不够准确,因此在这里整理一下。

为什么会有-Objc
这个问题还得从静态库文件说起,我们接触到的静态库就是.a文件。比如你写了一个数学运算的模块,里面包含 Add.m,Minus.m,Multiply.m,Divide.m四个源文件,编译之后生成了相应的四个.o文件, 使用 libtool -static -o../calculate.a *.o  命令就生成calculate.a,就是静态库文件。

 
此时Lib项目结构如下:
|--calculate
    |-- Add.m
    |-- Minus.m
    |-- Multiply.m
    |-- Divide.M
 
我们的项目是如何使用静态库文件的呢

一个代码文件引用了另一个代码文件中定义的符号时,在编译阶段,这些未定义的符号undefined symbol 会被写入object文件(即目标文件)。下一步进行链接时,linker会解析这些undefined symbol并把相应的文件取出来放入最终的可执行文件。

 
结合我们的例子,如果把上一步生成的calculate.a文件加入了项目,并且在某个文件中使用了Add这个类,那么链接阶段linker会从calculate.a中取出Add.o加入到最终生成的文件中。注意到这里,linker是按需加载的,这样最终生成的可执行文件体积不会太大。

 
其实以上都是很理想的情况。

如果你在原有Lib项目中针对Add增加了一个Category,并命名为SuperAdd.m,那么会发生什么呢?
 
此时Lib项目结构如下:
|--calculate
    |-- SuperAdd.m(category了Add)
    |-- Add.m
    |-- Minus.m
    |-- Multiply.m
    |-- Divide.m
 
问题来了
由于oc独有的基于消息的方法调用机制,导致编译阶段linker只认类而不管方法。SuperAdd其实只是一堆方法的集合而已,因此如果你在项目的源码里调用了SuperAdd中定义的方法,链接时这些方法也不会被打入最终的可执行文件,因此运行时会报‘unrecognized selector’。怎么解决呢?方法就是加入 -Objc,使得Category可以被linker拿到。

 
这里特别指出,如果不使用-Objc这个flag,是方法找不到,而不是类找不到,那样编译阶段就报错了,又如何会抛出‘unrecognized selector’的运行时错误。(很多文章在一点上说错了)

 
那么是否就一切万事大吉了,显然还有问题。

Category的基础类出现在另一个库中了

 
如果在你的lib项目中,你又添加了一个Category,GeoCalc.m扩展了另一个Geo库中的类Geometry, 此时Lib项目结构如下:

|--calculate
    |-- SuperAdd.m
    |-- Add.m
    |-- Minus.m
    |-- Multiply.m
    |-- Divide.m
    |-- GeoCalc.m (category了Geo.a中的Geometry)
 
calculate库中不存在GeoCalc的基础类Geometry,如果你的项目也同时加入了Geo库,点击运行,会发生什么呢? linker有一个bug,如果一个类别的基础类不在本库中,那么即使使用-Objc这个flag,类别的方法集也不会被linker取到,所以运行时就报‘unrecognized selector’了。
 
那么怎么解决呢? -load_all就有用武之地了。使用这个flag之后,所有库中的类与category都会被pull到可执行文件。这里必须严谨的指出,至于load_all具体是如何让linker可以拿到这种“跨库Category”的,这就是属于编译器本身的一个问题了。只能说-load_all确实修正了基础类不在本库情况下的Category读取问题,并且使用这个flag确实所有库中的类与Category都被pull到了最终的文件中。不过,使用load_all会使最终文件很臃肿,必要及非必要的类都被打入了最终的可执行文件,太臃肿了,那该如何解决呢?

 
最后一个角色-force_load出场了,它也可以修复编译器只使用Objc情况下出现的bug,但是跟load_all有不一样的地方。 从名字就可以看出,这个flag是按需指定加载的。

 
这里,我再说明一点,并不是说xcode4.2以后就不必使用-load_all,-force_load了,笔者使用的是xcode 8.2.1依然有效。

 
总结:

-Objc,  使linker可以从静态库中pull到Category,适用于存在Category的库
-load_all, 使linker可以pull到所有库中的类与Category,适用于Category的基础类不存在于本库中的情况

-force_load, 与-load_all相似,但可以按需加载指定库中的类与Category,避免执行包过大
 
最后说一下公司IOS项目中所遇到的问题。加入一个视频录制的库之后,由于之前设置了-load_all,所有库中的类与Category都会被pull,出现了symbol重复,所以duplicate symbol,于是去掉了load_all。编译倒是通过了,但是由于项目中有一个库Category了另一个库中的类,导致方法没能打入到最终的执行文件,报‘unrecognized selector’。解决方法:force_load新加入的库。

 
 
 
 
   
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  IOS 移动开发