关闭

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

标签: iOSlibrarycategory-all_load
329人阅读 评论(0) 收藏 举报
分类:

编译器把源文件(.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


0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:334次
    • 积分:14
    • 等级:
    • 排名:千里之外
    • 原创:0篇
    • 转载:0篇
    • 译文:1篇
    • 评论:0条
    文章分类
    文章存档