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

公司的一个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新加入的库。

 

 

 

 

   


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值