Answers:
解决方案:从Xcode 4.2开始,您只需要转到与库链接的应用程序(而不是库本身),然后在“项目浏览器”中单击项目,单击应用程序的目标,然后构建设置,然后搜索“其他链接器标志”,单击+按钮,然后添加“ -ObjC”。不再需要“ -all_load”和“ -force_load”。
详细信息: 我在各种论坛,博客和Apple文档中找到了一些答案。现在,我尝试对搜索和实验做一个简短的总结。
问题是由以下原因引起的(来自Apple技术问答QA1490 https://developer.apple.com/library/content/qa/qa1490/_index.html的引用):
Objective-C并未为每个函数(或方法,在Objective-C中)定义链接器符号-而是仅为每个类生成链接器符号。如果使用类别扩展现有的类,则链接器不知道将核心类实现的对象代码与类别实现相关联。这样可以防止在结果应用程序中创建的对象响应类别中定义的选择器。
及其解决方案:
若要解决此问题,静态库应将-ObjC选项传递给链接器。该标志使链接器加载库中定义Objective-C类或类别的每个对象文件。尽管此选项通常会导致更大的可执行文件(由于将附加的目标代码加载到应用程序中),但它将允许成功创建包含现有类类别的有效Objective-C静态库。
iPhone开发常见问题解答中也有建议:
如何链接静态库中的所有Objective-C类?将“其他链接器标志”构建设置设置为-ObjC。
和标志说明:
- all_load负载静态归档库中的所有成员。
- ObjC负载静态归档库中的所有成员实现的Objective-C类或类别。
- force_load(path_to_archive)加载指定的静态归档库的所有成员。注意:-all_load强制加载所有档案的所有成员。此选项使您可以定位特定的存档。
*我们可以使用force_load来减小应用程序二进制文件的大小,并避免在某些情况下all_load可能引起的冲突。
是的,它与添加到项目中的* .a文件一起使用。但是我在将lib项目添加为直接依赖项方面遇到了麻烦。但是后来我发现这是我的错-直接依赖项目可能未正确添加。当我删除它并再次添加步骤时:
之后一切正常。在我的情况下,“-ObjC”标志就足够了。
我也对http://iphonedevelopmentexperiences.blogspot.com/2010/03/categories-in-static-library.html博客中的想法感兴趣。作者说他可以使用lib中的类别,而无需设置-all_load或-ObjC标志。他只是将h / m文件类别添加到空的虚拟类接口/实现中,以强制链接器使用此文件。是的,这个技巧可以完成任务。
但是作者还说他甚至没有实例化虚拟对象。嗯……我发现我们应该从类别文件中显式调用一些“真实”代码。因此,至少应调用类函数。而且我们甚至不需要虚拟类。单c函数执行相同的操作。
因此,如果我们将lib文件编写为:
// 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(); 在App项目中的任何地方,然后在任何类中,我们都可以使用logSelf类别方法;
[self logSelf];
还有更多关于主题的博客:
http://t-machine.org/index.php/2009/10/13/how-to-make-an-iphone-static-library-part-1/
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文件中复制单个.a文件相比,复制一个.a文件要容易得多(类似于Java,在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部分始终是链接阶段的一部分。因此,如果加载包含类别的目标文件是因为其中的任何符号都被视为“正在使用”(它是一个类,一个函数,是一个全局变量),那么类别也会被加载,并且将在运行时可用。但是,如果未加载目标文件本身,则其中的类别在运行时将不可用。包含目标文件仅是类别从来没有加载,因为它包含任何符号链接将永远考虑“使用中”。这就是整个问题。
已经提出了几种解决方案,现在您知道所有这些如何结合使用,让我们对提出的解决方案进行另一种观察:
一种解决方案是添加-all_load
到链接器调用。该链接器标记实际上将做什么?实际上,它告诉链接器以下内容:“ 加载所有归档文件的所有目标文件,无论您是否看到任何正在使用的符号 ”。当然,这是可行的;但是它也可能会生成相当大的二进制文件。
另一个解决方案是将添加-force_load
到链接器调用中,包括归档文件的路径。该标志的工作方式与完全相同-all_load
,但仅适用于指定的存档。当然这也将起作用。
最受欢迎的解决方案是添加-ObjC
到链接器调用。该链接器标记实际上将做什么?该标志告诉链接器“ 如果发现它们包含任何Obj-C代码,则从所有归档文件中加载所有目标文件 ”。并且“任何Obj-C代码”都包括类别。这也将正常工作,并且不会强制加载不包含Obj-C代码的目标文件(这些文件仍仅按需加载)。
另一个解决方案是相当新的Xcode构建设置Perform Single-Object Prelink
。此设置会做什么?如果启用,则所有目标文件(请记住,每个源文件都有一个)被合并到一个目标文件(不是真正的链接,因此名为PreLink)和这个单一目标文件(有时也称为“主对象”)中文件”),然后将其添加到存档中。如果现在考虑使用主目标文件中的任何符号,则将整个主目标文件视为已使用,因此始终会加载其所有Objective-C部分。而且,由于类是普通符号,因此使用此类静态库中的单个类来获取所有类别就足够了。
最终的解决方案是弗拉基米尔(Vladimir)在回答的最后添加的技巧。在任何仅声明类别的源文件中放置一个“ 假符号 ”。如果要在运行时使用任何类别,请确保在编译时以某种方式引用伪造的符号,因为这将导致链接器以及所有其中的Obj-C代码加载目标文件。例如,它可以是带有空函数体的函数(调用时将不执行任何操作),也可以是访问的全局变量(例如,全局变量int
一旦读取或写入,就足够了)。与上述所有其他解决方案不同,此解决方案将对哪些类别在运行时可用的控制权移交给了已编译的代码(如果希望它们链接并可用,则访问该符号,否则将不访问该符号,并且链接器将忽略它)。
那是所有人。
哦,等等,还有一件事:
链接器有一个名为的选项-dead_strip
。此选项有什么作用?如果链接器决定加载目标文件,则无论是否使用,目标文件的所有符号都将成为链接二进制文件的一部分。例如,一个目标文件包含100个函数,但是二进制文件仅使用其中一个函数,因为目标文件是整体添加的,或者根本不添加,所以所有100个函数仍会添加到二进制文件中。链接程序通常不支持部分添加目标文件。
但是,如果您告诉链接器为“死区”,则链接器将首先将所有目标文件添加到二进制文件中,解析所有引用,最后扫描二进制文件中未使用的符号(或仅被未使用的其他符号使用)用)。然后,在优化阶段将所有未使用的符号删除。在上面的示例中,再次删除了99个未使用的功能。如果您使用或之类的选项-load_all
,这将非常有用,-force_load
或者Perform Single-Object Prelink
因为在某些情况下这些选项很容易使二进制大小急剧膨胀,并且死区剥离会再次删除未使用的代码和数据。
死区剥离对于C代码非常有效(例如,按预期方式删除了未使用的函数,变量和常量),对于C ++也非常有效(例如,删除了未使用的类)。这不是完美的,在某些情况下,即使可以删除某些符号也不会将其删除,但是在大多数情况下,对于这些语言来说,效果很好。
那Obj-C呢?忘掉它!Obj-C没有死皮。由于Obj-C是一种运行时功能语言,因此编译器无法在编译时说出符号是否确实在使用中。例如,如果没有直接引用它的代码,则不使用Obj-C类,对吗?错误!您可以动态构建一个包含类名的字符串,为该名称请求一个类指针,并动态分配该类。例如代替
MyCoolClass * mcc = [[MyCoolClass alloc] init];
我也可以写
NSString * cname = @"CoolClass";
NSString * cnameFull = [NSString stringWithFormat:@"My%@", cname];
Class mmcClass = NSClassFromString(cnameFull);
id mmc = [[mmcClass alloc] init];
在这两种情况下,mmc
都引用了“ MyCoolClass”类的对象,但是在第二个代码示例中,没有直接引用该类(甚至没有将类名作为静态字符串)。一切仅在运行时发生。这就是即使类是真正实符号。对于类别来说甚至更糟,因为它们甚至不是真正的符号。
因此,如果您有一个包含数百个对象的静态库,但是大多数二进制文件仅需要其中一些,则您可能不希望使用上述解决方案(1)到(4)。否则,即使其中绝大部分都没有使用过,您最终还是会得到包含所有这些类的很大的二进制文件。对于类,您通常根本不需要任何特殊的解决方案,因为类具有真实的符号,并且只要您直接引用它们即可(而不是在第二个代码示例中),链接器会很好地自行识别它们的用法。但是,对于类别,请考虑解决方案(5),因为它可以仅包含您真正需要的类别。
例如,如果您想要NSData的类别,例如向其添加压缩/解压缩方法,则可以创建一个头文件:
// 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()
代码,该属性将使编译器和链接器相信它已被调用,即使没有调用也是如此。
最后一个提示:
如果添加-whyload
到最终的链接调用中,则链接器将在构建日志中打印由于使用了哪个符号而从哪个库中加载了哪个目标文件。它将仅打印考虑使用的第一个符号,但不一定是使用该目标文件的唯一符号。
-whyload
,尝试调试链接器为什么要执行某些操作可能非常困难!
Dead Code Stripping
在Build Settings>Linking
。与-dead_strip
添加的内容相同Other Linker Flags
吗?
-ObjC
,所以我尝试了您的破解,但是它抱怨"import_NSString_jsonObject()", referenced from: importCategories() in main.o ld: symbol(s) not found
。我放入import_NSString_jsonObject
名为的嵌入式框架Utility
,并在末尾添加#import <Utility/Utility.h>
with __attribute__
语句AppDelegate.h
。
LLVM中已解决此问题。该修补程序作为LLVM 2.9的一部分提供。包含此修补程序的第一个Xcode版本是LLVM 3.0附带的Xcode 4.2。仍需要使用XCode 4.2时不再使用-all_load
或-force_load
-ObjC
。
-ObjC
标志仍然是必需的,并且将一直存在。解决方法是使用-all_load
或-force_load
。而且不再需要了。我在上面修正了我的答案。
编译静态库时,需要执行以下操作以完全解决此问题:
转到Xcode构建设置,然后将“执行单对象预链接”设置为“是”或
GENERATE_MASTER_OBJECT_FILE = YES
在构建配置文件中。
默认情况下,链接器为每个.m文件生成一个.o文件。因此类别获得不同的.o文件。链接器查看静态库.o文件时,不会创建每个类的所有符号的索引(运行时无关紧要)。
该指令将要求链接器将所有对象打包到一个大的.o文件中,并由此强制处理静态库的链接器获取所有类类别的索引。
希望可以澄清这一点。
每当出现静态库链接讨论时,很少提及的一个因素是,您还必须在构建阶段->复制文件中包括类别本身,并编译静态库本身的源代码。
苹果公司也没有在他们最近发布的iOS中使用“静态库”中强调这一事实。
我花了一整天的时间来尝试-objC和-all_load等的各种变化。.但是什么都没出现。. 这个问题引起了我的注意。(不要误会我..您仍然必须做-objC东西..但不仅限于此)。
另一个始终对我有帮助的动作是,我总是先自行构建包含的静态库。然后再构建封闭的应用程序。