Objective-C categories in static library
你能指导我如何正确地将静态库链接到iPhone项目吗?我使用添加到应用程序项目中的静态库项目作为直接依赖项(target->general->direct dependencies),并且所有的工作都正常,但类别除外。静态库中定义的类别在应用程序中不起作用。
所以我的问题是如何将带有某些类别的静态库添加到其他项目中?
一般来说,使用其他项目的应用程序内项目代码的最佳实践是什么?
解决方案:从Xcode4.2开始,您只需转到与库链接的应用程序(而不是库本身),然后在项目导航器中单击项目,单击应用程序的目标,然后构建设置,然后搜索"其他链接器标志",单击+按钮,然后添加"-objc"。-不再需要所有"加载"和"强制加载"。
细节:我在各种论坛、博客和苹果文档中找到了一些答案。现在我试着对我的搜索和实验做一个简短的总结。
问题是由以下原因引起的(引用自Apple Technical Q&A QA1490 https://developer.apple.com/library/content/q a/qa1490/_index.html):
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.
他们的解决方案是:
To resolve this issue, the static
library should pass the -ObjC option
to the linker. This flag causes the
linker to load every object file in
the library that defines an
Objective-C class or category. While
this option will typically result in a
larger executable (due to additional
object code loaded into the
application), it will allow the
successful creation of effective
Objective-C static libraries that
contain categories on existing
classes.
在iPhone开发常见问题解答中也有建议:
How do I link all the Objective-C
classes in a static library? Set the
Other Linker Flags build setting to
-ObjC.
和标志说明:
-all_load Loads all members of static archive libraries.
-ObjC Loads all members of static archive libraries that implement an
Objective-C class or category.-force_load (path_to_archive) Loads all members of the specified static
archive library. Note: -all_load
forces all members of all archives to
be loaded. This option allows you to
target a specific archive.
*我们可以使用强制加载来减少应用程序二进制文件的大小,并避免在某些情况下所有加载都可能导致的冲突。
是的,它可以处理添加到项目中的*.a文件。然而,我对作为直接依赖添加的lib项目有困难。但后来我发现这是我的错——直接依赖项目可能没有正确添加。当我删除它并用步骤再次添加时:
之后一切正常。"-在我的情况下,objc"标志就足够了。
我也对来自http://iphonedevelopmentexperiences.blogspot.com/2010/03/categories-in-static-library.html博客的想法感兴趣。作者说他可以使用lib中的category而不设置-all-load或-objc标志。他只是将空的虚拟类接口/实现添加到H/M类文件中,以强制链接器使用该文件。是的,这个把戏就可以了。
但作者也说他甚至没有实例化虚拟对象。嗯……正如我发现的,我们应该从类别文件中显式地调用一些"真正的"代码。所以至少应该调用类函数。我们甚至不需要假班。单C函数的作用相同。
因此,如果我们将lib文件写为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // mylib.h void useMyLib(); @interface NSObject (Logger) -(void)logSelf; @end // mylib.m void useMyLib(){ NSLog(@"do nothing, just for make mylib linked"); } @implementation NSObject (Logger) -(void)logSelf{ NSLog(@"self is:%@", [self description]); } @end |
如果我们调用usemylib();应用程序项目中的任何地方那么在任何类中,我们都可以使用logself-category方法;
1 | [self logSelf]; |
更多关于主题的博客:
http://blog.costan.us/2009/12/fat-iphone-static-libraries-device-and.html
弗拉基米尔的回答实际上相当不错,不过,我想在此提供更多的背景知识。也许有一天,有人会找到我的答复,可能会发现它有帮助。好的。
编译器将源文件(.c,.cc,.cpp,.m)转换为对象文件(.o)。每个源文件有一个对象文件。对象文件包含符号、代码和数据。操作系统无法直接使用对象文件。好的。
现在,在构建动态库(.dylib)、框架、可加载包(.bundle)或可执行二进制文件时,链接器将这些对象文件链接在一起,以生成操作系统认为"可用"的内容,例如,可以直接加载到特定内存地址的内容。好的。
但是,在构建静态库时,所有这些对象文件都只是简单地添加到一个大的存档文件中,因此扩展了静态库(.a用于存档)。所以.a文件只不过是对象(.o)文件的存档。可以想象一个tar存档或者一个没有压缩的zip存档。只复制一个文件,比一堆.O文件(类似于Java,打包的.class文件到.jar存档以便于分发)更容易。好的。
当将二进制文件链接到静态库(=存档)时,链接器将获得存档中所有符号的表,并检查这些符号中的哪一个是由二进制文件引用的。只有包含引用符号的对象文件实际上由链接器加载,并由链接过程来考虑。例如,如果存档有50个对象文件,但只有20个包含二进制使用的符号,则只有20个由链接器加载,其他30在链接过程中被完全忽略。好的。
对于C和C++代码来说,这是很好的,因为这些语言在编译时尽可能地做(尽管C++也有一些运行时的特性)。但是,obj-c是一种不同的语言。obj-c在很大程度上依赖于运行时特性,许多obj-c特性实际上只是运行时特性。obj-c类实际上有类似于c函数或全局c变量的符号(至少在当前obj-c运行时)。链接器可以查看某个类是否被引用,因此它可以确定某个类是否在使用中。如果使用静态库中对象文件的类,则链接器将加载此对象文件,因为链接器看到正在使用的符号。类别是运行时专用的功能,类别不是类或函数之类的符号,这也意味着链接器无法确定类别是否在使用中。好的。
如果链接器加载包含obj-c代码的对象文件,则其所有obj-c部分始终是链接阶段的一部分。因此,如果加载了包含类别的对象文件,因为其中的任何符号都被视为"正在使用"(无论是类、函数还是全局变量),则类别也将被加载,并在运行时可用。但是,如果没有加载对象文件本身,那么其中的类别在运行时将不可用。永远不会加载只包含类别的对象文件,因为它不包含链接器认为"正在使用"的符号。这就是这里的全部问题。好的。
已经提出了几个解决方案,现在您已经了解了所有这些解决方案的作用,让我们再看一看建议的解决方案:好的。
一种解决方案是将
另一种解决方案是将
最流行的解决方案是将
另一种解决方案是新的Xcode构建设置
最后的解决办法是弗拉迪米尔在回答的最后加了一个窍门。在任何源文件中放置一个"假符号",只声明类别。如果您想在运行时使用任何类别,请确保在编译时以某种方式引用假符号,因为这会导致对象文件被链接器加载,因此也会在其中加载所有的Obj-C代码。例如,它可以是一个具有空函数体的函数(在被调用时不会做任何事情),也可以是一个可访问的全局变量(例如,一次读取或写入全局
这是所有人。好的。
哦,等等,还有一件事:链接器有一个名为
但是,如果告诉链接器"死区",链接器将首先将所有对象文件添加到二进制文件中,解析所有引用,最后扫描二进制文件以查找未使用的符号(或仅由未使用的其他符号使用)。所有未使用的符号将作为优化阶段的一部分删除。在上面的示例中,99个未使用的函数将再次被删除。如果您使用诸如
死剥对于C代码非常有效(如未使用的函数、变量和常量按预期移除),并且它对C++也非常好(例如,未使用的类被删除)。它并不完美,在某些情况下,一些符号不会被删除,即使删除它们是可以的,但在大多数情况下,它对这些语言都非常有效。好的。
OBJ-C怎么样?算了吧!对于obj-c没有死板的剥离。由于obj-c是一种运行时功能语言,编译器在编译时不能说一个符号是否真的在使用。例如,如果没有直接引用某个obj-c类的代码,则该类不在使用中,对吗?错了!您可以动态构建包含类名的字符串,为该名称请求一个类指针,并动态分配类。例如,而不是好的。
1 | MyCoolClass * mcc = [[MyCoolClass alloc] init]; |
我也会写好的。
1 2 3 4 | NSString * cname = @"CoolClass"; NSString * cnameFull = [NSString stringWithFormat:@"My%@", cname]; Class mmcClass = NSClassFromString(cnameFull); id mmc = [[mmcClass alloc] init]; |
在这两种情况下,
因此,如果您有一个包含数百个对象的静态库,而大多数二进制文件只需要其中的一部分,那么您可能不喜欢使用上面的解决方案(1)到(4)。否则,最终会得到包含所有这些类的非常大的二进制文件,即使它们中的大多数从未被使用过。对于类,通常根本不需要任何特殊的解决方案,因为类有真正的符号,只要直接引用它们(不像在第二个代码示例中那样),链接器就可以很好地识别它们的用法。但是,对于类别,考虑解决方案(5),因为它可以只包括您真正需要的类别。好的。
例如,如果要为nsdata创建一个类别,例如向其添加压缩/解压方法,则需要创建一个头文件:好的。
1 2 3 4 5 6 7 | // NSData+Compress.h @interface NSData (Compression) - (NSData *)compressedData; - (NSData *)decompressedData; @end void import_NSData_Compression ( ); |
以及一个实现文件好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // NSData+Compress @implementation NSData (Compression) - (NSData *)compressedData { // ... magic ... } - (NSData *)decompressedData { // ... magic ... } @end void import_NSData_Compression ( ) { } |
现在,只要确保代码中的任何地方调用
1 2 3 4 5 | __attribute__((used)) static void importCategories () { import_NSData_Compression(); // add more import calls here } |
您不必在代码中调用
最后提示:如果您将
此问题已在LLVM中修复。修复作为llvm 2.9的一部分发布,包含修复的第一个xcode版本是xcode 4.2,与llvm 3.0一起发布。使用xcode 4.2
在编译静态库时,您需要执行以下操作来完全解决此问题:
要么转到XCuffBudio设置,然后将单个对象预链接设置为"是"或"是"。生成配置文件中的
默认情况下,链接器为每个.m文件生成一个.o文件。所以类别得到不同的.o文件。当链接器查看一个静态的library.o文件时,它不会为每个类创建所有符号的索引(运行时将创建,无所谓)。
此指令将要求链接器将所有对象打包到一个大的.o文件中,并由此强制处理静态库的链接器获取所有类类别的索引。
希望能澄清这一点。
每当出现静态库链接讨论时,很少提到的一个因素是,您还必须在静态库本身的构建阶段->复制文件和编译源中包含类别本身。
苹果在最近发布的iOS静态库中也没有强调这一点。
我花了一整天的时间尝试各种各样的变化,比如Objc和Aull Load等等。但没有任何东西出来。这个问题引起了我的注意。(别误会我的意思。你仍然必须做-Objc的东西…但不仅仅是这样。
另外一个总是帮助我的动作是,我总是首先自己构建包含的静态库。然后,我建立了封闭的应用程序。
您可能需要在静态库的"public"头中输入类别:import"mystaticlib.h"