标志 -Objc 、 -all_load 、 -force_load 笔记.
有时候经常会遇到在导入第三方库的时候需要在 Other Linker Flags 中添加 -Objc 标志、-all_load 标志、-force_load 标志。为什么要加这些标志?
原因和解决:
OC 没有为每个函数(或者方法)定义链接符号,它只为每个类创建链接符号。这样当在一个静态库中使用类别来扩展已有类的时候,链接器不知道如何把类原有的方法和类别中的方法整合起来,就会导致你调用类别中的方法时,出现 "selector not recognized",也就是找不到方法定义的错误。为了解决这个问题,引入了-ObjC 标志,它的作用就是将静态库中所有的和对象相关的文件都加载进来。
在 64 位的 Mac 系统或者 iOS 系统下,链接器有一个 bug,会导致只包含有类别的静态库无法使用 -ObjC 标志来加载文件。变通方法是使用 -all_load 或者 -force_load 标志,它们的作用都是加载静态库中所有文件,不过 -all_load 作用于所有的库,而 -force_load 后面必须要指定具体的文件。
摘自:
Build Settings中的变量@rpath,@loader_path,@executable_path
@rpath 和前面两个不同, 它只是一个保存着一个或多个路径的变量. 比如 XPSSO.dylib 被两个 .app 使用, 且被包含的路径不同。比如:
softA.app/Contents/MacOS/dylib/XPSSO.dylib
softB.app/Contents/MacOS/Frameworks/XPSSO.dylib
将 XPSSO.dylib 的 INSTALL_PATH 设置成 @loader_path/../dylib 或 @loader_path/../Frameworks 都只能满足其中一个 .app 的需求. 要解决这个问题, 就可以用 @rpath. 将 XPSSO.dylib 的 INSTALL_PATH 设置成 @rpath, 然后在编译 softA.app, softB.app 时分别指定 @rpath 为 @loader_path/../dylib, @loader_path/../Frameworks, 问题得到了解决. @rpath 的另一个优点是可以设置多个路径. 如果 softA.app 还需要使用另一个 .plugin (假设它的 INSTALL_PATH 也设置成了 @rpath), 位于 @loader_path/../plugin, 把这个路径加到 @rpath 即可.
XPSSO.dylib的Build Settings中设置Installation Directory
在 softA.app或softB.app 中设置 Runpath Search Paths(对应了@rpath)
今天遇到一个问题,在使用一个第三方sdk的时候,报错并闪退,但是注销这个sdk的代码就没问题,所以基本确定是sdk的问题,查看报错日志:
2016-09-18 10:39:31.409 sengoku[2620:98676] +[NSData base64DataFromString:]: unrecognized selector sent to class 0x10e3c5110
2016-09-18 10:39:31.424 sengoku[2620:98676] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[NSData base64DataFromString:]: unrecognized selector sent to class 0x10e3c5110'
一般这种都是因为没有这个函数,比如有的时候你自己非法调用了一个函数,但是这个函数并不属于这个类中的函数时,会报unrecognized selector,这时候首先要看下你调用这个函数的对象的类型。
比如NSString的对象调用了NSData的函数,这样就会报错,这时候看报错调用函数对象即可,所以这个看到是[NSData base64DataFromString:]这个函数报错,而NSData自己是没有这个函数的,所以确定了是这个函数的错误。
屏幕快照 2016-09-18 10.41.30.png
因为是第三方库,那么运行Demo就可以,这里报错,解决方法就是在项目的Build setting里面的Linking选项的Other Linker Flags加上
-ObjC
这个参数。
45eed87171c24ca8a3d38bfe93495274.png
错误原因
之所以使用该标志,和Objective-C的一个重要特性:类别(category)有关。根据官方的解释,Unix的标准静态库实现和Objective-C的动态特性之间有一些冲突:Objective-C没有为每个函数(或者方法)定义链接符号,它只为每个类创建链接符号。
这样当在一个静态库中使用类别来扩展已有类的时候,链接器不知道如何把类原有的方法和类别中的方法整合起来,就会导致你调用类别中的方法时,出现"selector not recognized",也就是找不到方法定义的错误。
为了解决这个问题,引入了-ObjC标志,它的作用就是将静态库中所有的和对象相关的文件都加载进来。
通俗来讲就是因为这些方法比如这里的base64DataFromString是这个静态库里面自己拓展的,而OC是没有把这个base64DataFromString方法和NSData整合,所以需要手动加上-ObjC这个标识。
为什么不在每个工程都加上呢,是因为加上这个相当于把静态库所有文件对象都加载进去,软件包会变大。
潜在问题
加上-ObjC之后,是可以全部都加载进来,但是也有可能会报duplicate symbol这个错误,这个错误意思是重复了,就是他们在静态库里面的类别名字恰好和你工程自己增加的类别名字重复了或其它第三方库中的类名重名了,分不清是哪个了,所以会报这个错,解决办法只能修改重复的类别名字了,所以以后自己写的时候,最常见的作法就是给类名加个前缀。以避免别人用你的库时,产生 duplicate symbol 的问题。
-all_load、-force_load、-ObjC的区别
-ObjC已经说了,就是为了把静态库的类别加载进来,但是如果类库中只有category没有类的时候这些category还是加载不进来。
而-all_load和-force-load就是为了解决这个问题。-all_load会强制链接器把目标文件都加载进来,即使没有objc代码。-force_load后面必须跟一个只想静态库的路径。
所以-all_load比-ObjC加载的更多,软件包更大,上面说的那个潜在问题也是存在的,而-force_load则是开发者自己指定加载哪个静态库,比较好点,但是需要指定静态库的路径,比较麻烦。
-all_load和-ObjC使用方式一样,就是在项目的other linker flag里面加上就行了
-force_load需要加路径使用
-force_load $(SRCROOT)/framework/Debug/UIEMultiAccess.framework/UIEMultiAccess
如果是多个库,除了加路径,每个都要加上这个关键词,不能写成-force_load libA.a libB.a libC.a
正确写法是
-force_load libA.a
-force_load libB.a
-force_load libC.a
其他
在other Linker Flag里面有时还会有其他的,比如-lxml2,-lz,这些是为了链接库,所以接sdk的时候看好需要配置的条件。
预编译头中加__OBJC__是因为在一个OC工程中,可能包含.m、.mm、.c、.cpp四类编译文件,这四类文件均会引用.pch预编译头。在编译.c、.cpp时,因为语法不兼容OC,所以预编译头中不能包含objc代码。因为.pch是2类源文件共用的,所以在pch中,oc头文件要用__OBJC__包含起来。如下:(可以打开工程自己查看pch)