logo头像

Renbo’s Blog

Xcode中Other Linker Flags的作用

背景

在iOS开发中,经常会使用一些第三方的静态库(.a文件),有时候导入第三方类库 运行程序后你会发现:编译时可以正常编译,但运行时会app会闪退,报出selector not recognized的错误。

  • 原因:静态库(.a文件)中使用了Category,而Objective-C的链接器并不会直接把静态库中的分类加载到最后的可执行文件中。所以程序在运行时,因找不到分类中的方法而闪退。

  • 解决办法:

    1》一般的第三方库的开发文档中都会写出这种问题的解决方法:如,在Build Settings——》Linking——》Other Liker Fliags中添加 -ObjC-all_load-force_load 等。

    2》把类别文件添加进来(如果是第三方库,就不太现实)

    3》在每个分类的 @implementation 前添加这个自定义宏:define BRSYNTH_DUMMY_CLASS( )

    /**
        静态库中编写 Category 时的便利宏,用于解决 Category 方法从静态库中加载需要特别设置的问题。
        加入这个宏后,不需要再在 Xcode 的 Other Liker Fliags 中设置链接库参数(-Objc / -all_load / -force_load)
        *******************************************************************************
        使用:在静态库中每个分类的 @implementation 前添加这个宏
        Example:
            #import "NSString+BRAdd.h"
    
            BRSYNTH_DUMMY_CLASS(NSString_BRAdd)
            @implementation NSString (BRAdd)
            @end
     */
    #ifndef BRSYNTH_DUMMY_CLASS
    
        #define BRSYNTH_DUMMY_CLASS(_name_) \
        @interface BRSYNTH_DUMMY_CLASS_ ## _name_ : NSObject @end \
        @implementation BRSYNTH_DUMMY_CLASS_ ## _name_ @end
    
    #endif
    
    // 方法3是从源头上解决问题,需要修改库的源码。如果库是自己的,推荐这种方法。如果是别人的,只能使用方法1。
    

思考:为什么要这样做呢? 为什么在编译时没有问题运行时就会报错?

  • 到这里,首先我们先引入一个链接器的概念 >>>

链接器

还记得我们在学习C程序的时候,从C代码到可执行文件经历的步骤是:

源代码 > 预处理器 > 编译器 > 汇编器 > 机器码 > 链接器 > 可执行文件

在最后一步需要把.o文件和C语言运行库链接起来,这时候需要用到ld命令。源文件经过一系列处理以后,会生成对应的.obj文件,然后一个项目必然会有许多.obj文件,并且这些文件之间会有各种各样的联系,例如函数调用。

链接器做的事就是把这些目标文件和所用的一些库链接在一起形成一个完整的可执行文件。

通过这个流程你也应该知道为什么在编译的过程中没事而在运行的时候就会报错了。

那我们为什么要设置Other Linker Flags呢?

  • 因为Other Linker Flags其实就是链接器工作时除了默认参数外的其他参数。

闪退的原因

苹果官方Q&A上有这么一段话:

The “selector not recognized” runtime exception occurs due to an issue between the implementation of standard UNIX static libraries, the linker and the dynamic nature of Objective-C. Objective-C does not define linker symbols for each function (or method, in Objective-C) - instead, linker symbols are only generated for each class. If you extend a pre-existing class with categories, the linker does not know to associate the object code of the core class implementation and the category implementation. This prevents objects created in the resulting application from responding to a selector that is defined in the category.

译:运行时的异常是由于静态库,链接器,与OC语言的动态特性之间的问题,Objective-C的链接器并不会为每个方法建立符号表,而只是对每一个类创建了符号表。如果一个类有了分类,那么链接器就不会将核心类与分类之间的代码进行合并,这样的话,在最后的可执行文件中,就会缺少分类里的代码,这样函数调用就失败了。

链接器参数

在前面我们说如果出现问题要在Other Linker Flags中加入 -ObjC 或者 -all_load 或者 -force_load ,我们为什么要加入这样的参数呢,他们究竟做了什么事呢?下面就是对这三个参数的一个讲解。

  • -ObjC

    作用是:让链接器把静态库中所有的Objective-C类和分类都加载到最后的可执行文件中。

    如果静态库中有类和category,只能加入这个参数才行;

    如果静态库中只有分类而没有类,-Objc就失效了,这就需要使用-all_load或者-force_load了。

  • -all_load

    作用是:让链接器把所有找到的目标文件(可能是多个静态库文件)都加载到可执行文件中。

    这个参数也有一个弊端,那就是你使用了不止一个静态库文件,那么你很有可能会遇到ld: duplicate symbol错误,因为不同的库文件里面可能会有相同的目标文件,所以建议在遇到-ObjC失效的情况下使用-force_load参数。

  • -force_load

    所做的事情跟-all_load其实是一样的,只是-force_load需要指定要进行全部加载的库文件的路径,这样的话,你就只是完全加载了一个库文件,不影响其余库文件的按需加载。

总结

个人建议ObjC与force_load搭配使用比较好。

上一篇