怎样让静态库(static library)中的category变得可用

翻译 2015年11月20日 19:09:45

编译器把源文件(.c,.cc,.cpp,.m)转化成对象文件(.o),源文件和对象文件是一一对应。对象文件(object file)里包含了符号、代码和数据,它并不直接被操作系统使用。

当你组建(build)一个动态库(.dylib)、一个framework、一个可加载的bundle(bundle)或者一个可执行的二进制文件的时候,这些文件被链接器链接到一起来生成一些操作系统认为“可用的”的东西,例如一些可以直接载入指定内存地址的东西。

然而,当你组建静态库(static library)的时候,所有的对象文件被简单的添加到一个大的归档文件,这就是.a(archive)文件的由来。所以.a文件就是一些对象文件的归档(想象一下没有经过压缩的tar归档或者zip归档)。拷贝单个.a文件要比拷贝一堆.o文件简单的多(java也是一样,为了使用方便你可以把一些.class文件放到一个.jar归档里)。

在把二进制链接到静态库(=archive)的时候,编译器会获得一张含有在归档里所有符号的表然后检查哪些符号被这些二进制文件引用了。只有包含被引用的符号的对象文件会被链接器真正的载入,并被链接进程处理。例如,如果你的文档中有50个对象文件,但是只有20个包含被二进制使用的符号,那么只有这20个文件会被链接器载入,另外30个会被链接进程完全忽略。

在C和C++代码里,这种机制能很好地工作,因为这些语言尽可能在编译时(C++也有一些runtime特性)去做这些事。然而Objective-C是一种不同的语言。OC非常依赖runtime特性,而且很多OC的特性都是只支持runtime的。OC类里面实际上也有类似于C函数或全局C变量的符号表(至少现在的OC runtime是这样)。编译器可以识别出一个类有没有被引用,从而确定这个类是不是被使用了。如果你使用了静态库里的对象文件中的类,这个对象文件就会被链接器载入,因为链接器发现它的一个符号被使用了。

类别是runtime下特有的特性,类别并不会像类或者函数一样被符号化,也就是说,编译器并不能检测到类别是不是被使用了。

如果链接器载入了一个包含OC代码的对象文件,这些OC代码的所有部分都是编译阶段的一部分。所以当一个包含类别的对象文件因为任何符号被认为“在使用”(可能是一个类,可能是一个函数,也可能是一个全局变量),它的类别也会被载入并在runtime中变得可用。一个只包含类别的对象文件里没有被编译器认为“在使用”的符号,所以是不会被载入的。

所以为了使在静态库中的类别能被使用,可以通过下面的5中方法解决:

1.    通过在Other Linker Flags添加-all_load,它会告诉编译器“对于所有文档中的所有对象文件,不管里面的符号有没有被用到,都载入”,这种方法确实可以,但是会产生比较大的二进制文件。

2.    另一种方法是添加-force_load和指定的路径,这种方法和-all_load很像,不同的是它只使用指定的归档。

3.    最受欢迎的方法是在Other Linker Flags中添加-ObjC,这个标识告诉编译器“如果你在文档里的对象文件中发现了OC代码,就把它载入“,Category里当然也有OC代码。使用这种方法不会载入任何没有OC代码的文件

4.    另一种解决方法是新Xcode里build setting中的 PerformSingle-Object PreLink,如果启用这个选项,所有的对象文件都会被合并成一个单文件(这不是真正的链接,所以叫做预链接),这个对象文件(有时被称做主对象文件(masterobject file))被添加到文档中。现在如果主对象文件中的任何符号被认为是“在使用”,整个主对象文件都会被认为在使用,这样它里面的OC部分就会被载入了。因为里面的类都被正常符号化了,所以能使从这样的静态库中使用所有的category。

5.    最后一种解决方法是在只有category的源文件里添加Fakesymbol。如果你想在runtime里使用category,一定要确保你以某种方法在编译时引用了fake symbol,这会使得对象文件以及它里面的OC代码被载入。例如,它可以是一个有空函数体的函数,也可以是一个被访问的全局变量(例如一个全局的int变量,只要它被读或者写了一次就足够了)。和上面其他的解决方法不一样,这种解决方法可以控制哪些category可以在runtime里被编译后的代码使用(可以通过使用这个符号,使它们被链接并变得可用;也可以不使用这个符号,这样链接器就会忽略它)。

 

如果你有一个包含上百个对象的静态库,但是你的二进制文件只使用了其中的几个,你应该避免使用1~4的方法。否则你会获得一个非常大的包含了所有类(可能这些类根本没被用到)的二进制文件。对于一个类别,你通常不用做特殊的处理;而对于一个类别,考虑一下第5中解决方案,它可以让你只包含你想要的类别。

 

例如:如果你想使用NSData的类别,添加两个方法compressionData和decompressionData,你可以创建一个这样的头文件。

// NSData+Compress.h
@interface NSData (Compression)
    - (NSData *)compressedData;
    - (NSData *)decompressedData;
@end
 
void import_NSData_Compression ( ); //注意这个方法*************

再添加一个这样的实现文件

// NSData+Compress
@implementation NSData (Compression)
    - (NSData *)compressedData 
    {
        // ... magic ...
    }
 
    - (NSData *)decompressedData
    {
        // ... magic ...
    }
@end
 
void import_NSData_Compression ( ) { } //注意这里***************

 

然后确保在你的代码里调用了import_NSData_Compression这个方法。是不是真的调用或者调用的次数并不重要,实际上你并不需要真的去调用它,只需要让编译器认为你调用了这个方法就行了。你可以吧这段代码添加到你的工程的任何地方

 

__attribute__((used)) static void importCategories ()
{
    import_NSData_Compression();
    // add more import calls here
}

 

你并不需要在你的代码里调用importCategories (),attribute将会让编译器认为它被调用了。

如果你添加了-whyload标识,链接器会打印组件日志告诉你“哪个二进制文件里的哪个对象文件因为哪一个符号被使用所以被载入了”。但是它只会打印第一个被认为是“使用中”的符号。

 

-dead strip对OC不适用,不感兴趣的可以不往下看了

编译器还有一个属性叫做–dead_strip,如果编译器决定再入一个对象文件,这个文件件中的所有符号都会变成链接后的二进制文件中的一部分,不管它们有没有被使用。比如有一个对象文件包含了100个函数,但是它们中只有一个被二进制文件使用了,所有的100个函数仍然会被添加到二进制文件里,因为对象文件只能被完整的添加或者不被添加。链接器不支持部分添加对象文件。

但是,如果你告诉链接器“dead strip”, 链接器首先会把所有的对象文件添加到二进制文件,解决所有的引用,然后扫描二进制文件中的符号是不是在被使用(或者是被一些没有被使用的符号使用)。所有的被找到的没有被使用的符号都会在优化阶段被移除。在上面的例子里,99个没有被使用的函数会被移除。如果你使用了-load_all,-force_load或者Perform Single-Object PreLink,这个标识会非常有用,因为这三个选项在一些情况下很容易增加二进制文件的大小,而dead strip将会移除那些没用的代码和数据。

Dead strip对于C代码能很好的工作(例如:像预期的那样去掉没用的函数、变量和常量),它在C++上也能工作的不错(例如:没用的类能够被移除)。虽然它并不完美,在一些情况下一些符号没有被移除,但是在大多数情况下它能在这些语言下很好地工作。

在OC
OC里面,并不支持deadstrp,因为OO是一种runtime特性的语言,编译器并不能在编译时确定符号是不是真的被使用了。例如,如果没有代码直接使用一个OC的类,那他就是没有被使用吗?当然不是,你可以动态组建一个包含类名的字符串,获得这个类的一个指针,并动态的执行alloc。

比如:

MyCoolClass * mcc = [[MyCoolClassalloc] init];

也可以写成

NSString * cname = @"CoolClass";

NSString * cnameFull = [NSStringstringWithFormat:@"My%@", cname];

Class mmcClass = NSClassFromString(cnameFull);

id mmc = [[mmcClass alloc]init];

在第二种方式里,并没有直接饮用这个类,所有的事情都在runtime进行。

 

 

原文链接http://stackoverflow.com/questions/2567498/objective-c-categories-in-static-library    

感谢作者Mecki


相关文章推荐

iOS中静态库(static library, .a文件)中的category变得可用

如果在静态库中定义了一个category的话,APP中直接使用的话,会出现"undefined symbols"的错误,明明程序中已经定义了啊,为什么呢?...

怎样让动态库(static library)中的category变得可用

推荐!

创建静态库Cocoa Touch Static Library

测试环境:Xcode 4.3.2 代码MyStaticLib, MyStaticLibUse 为了系统的安全,ios只允许使用静态库(静态连编到程序中),不能用动态库(程序运行时才加载到内存)...

iOS制作Static Library(静态库),实现多工程的连编

在iOS开发中,我们会发现一些偏底层或基础代码是直接可以复用的,当我们换一个项目,改变的只需要是偏上层的业务逻辑代码,所以我们可以把这部分 基础代码制作为一个静态库static library,并不断...

iPhone开发【二十五】使用静态库—第1篇(使用Cocoa Touch Static Library模板)

转载请注明出处,原文网址:http://blog.csdn.net/m_changgong/article/details/8308956 作者:张燕广 主要内容:1)演示使用静态库的第一种方法,即...

静态库(Static Library)和动态框架(Dynamic Framework)

静态库(.a文件),是一系列从源码编译的目标文件的集合,是源码的实现所对应的二进制。配合上公共的.h文件,可以获取到.a中暴露的方法或者成员变量,在最后编译app的时候.a将被链接到最终的可执行文件中...

使用Xcode创建Cocoa Touch Static Library(静态库)

首先科普一下静态库的相关知识: 程序编译一般需经预处理、编译、汇编和链接几个步骤。对于我们项目中的一些公共代码,如果想要对其进行复用,可以把这些代码编译成一个静态库文件。在链接步骤中,链接器会从...

iOS深入学习(创建静态库static library)

摘要 静态库文件可以有效的将功能封装和细节隐藏 在项目开发的过程中,经常使用静态库文件。例如两个公司之间业务交流,不可能把源代码都发送给另一个公司,这时候将私密内容打包成静态库,别人只能调用接口...

创建静态库static library

在项目开发的过程中,经常使用静态库文件。例如两个公司之间业务交流,不可能把源代码都发送给另一个公司,这时候将私密内容打包成静态库,别人只能调用接口,而不能知道其中实现的细节。 简介: 库是...
  • Vic__li
  • Vic__li
  • 2015年11月21日 18:50
  • 210

iOS深入学习(创建静态库static library)

在项目开发的过程中,经常使用静态库文件。例如两个公司之间业务交流,不可能把源代码都发送给另一个公司,这时候将私密内容打包成静态库,别人只能调用接口,而不能知道其中实现的细节。简介:库是一些没有main...
  • zz_mm
  • zz_mm
  • 2015年07月16日 14:11
  • 801
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:怎样让静态库(static library)中的category变得可用
举报原因:
原因补充:

(最多只允许输入30个字)