ios 静态库中的Objective-C类别

qcbq4gxm  于 2023-04-13  发布在  iOS
关注(0)|答案(6)|浏览(859)

你能指导我如何正确链接静态库到iPhone项目。我使用静态库项目添加到应用程序项目作为直接依赖(目标-〉一般-〉直接依赖)和所有工作正常,但类别。在静态库中定义的类别是不工作的应用程序。
所以我的问题是如何添加静态库与一些类别到其他项目?
一般来说,在其他项目的应用程序项目代码中使用的最佳实践是什么?

xfb7svmp

xfb7svmp1#

**解决方案:**从Xcode 4.2开始,您只需要转到链接库的应用(而不是库本身),在Project Navigator中点击项目,点击应用的目标,然后构建设置,然后搜索“Other Linker Flags”,点击+按钮,并添加'-ObjC'。
**详细信息:**我在各种论坛、博客和苹果文档中找到了一些答案。现在我试着对我的搜索和实验做一个简短的总结。

问题是由以下原因引起的(引用自Apple技术问答QA 1490 https://developer.apple.com/library/content/qa/qa1490/_index.html):
Objective-C没有为每个函数定义链接器符号(或方法,在Objective-C中)-相反,链接器符号只为每个类生成。如果你用类别扩展一个预先存在的类链接器不知道将核心类实现的目标代码与类别实现相关联。这将防止在结果应用程序中创建的对象响应在类别中定义的选择器。
他们的解决方案:
要解决此问题,静态库应将-ObjC选项传递给链接器。此标志会导致链接器加载库中定义Objective-C类或类别的每个对象文件。虽然此选项通常会导致较大的可执行文件(由于加载到应用程序中的附加目标代码),它将允许成功创建有效的Objective-C静态库,其中包含现有类的类别。
在iPhone开发常见问题中也有建议:
如何链接静态库中的所有Objective-C类?将Other Linker Flags构建设置设置为-ObjC。
和标志说明:
-all_load加载静态归档库的所有成员。
-ObjC加载实现Objective-C类或类别的静态归档库的所有成员。
-**force_load(path_to_archive)**加载指定静态归档库的所有成员。注意:-all_load强制加载所有存档的所有成员。此选项允许您以特定存档为目标。

  • 我们可以使用force_load来减少应用程序的二进制大小,并避免all_load在某些情况下可能导致的冲突。

是的,它可以处理添加到项目中的 *.a文件。然而,我在添加lib项目作为直接依赖时遇到了麻烦。但后来我发现这是我的错-直接依赖项目可能没有正确添加。当我删除它并重新添加时:
1.在app项目中拖放lib项目文件(或使用Project-〉Add to project...添加它)。
1.单击lib项目图标上的箭头-显示的mylib.a文件名,拖动此mylib.a文件并将其放入Target -〉Link Binary With Library组。
1.打开目标信息在第一页(一般)和添加我的库到依赖列表
在那之后,一切正常。“-ObjC”标志在我的情况下就足够了。
我也对http://iphonedevelopmentexperiences.blogspot.com/2010/03/categories-in-static-library.html博客的想法很感兴趣。作者说他可以在不设置-all_load或-ObjC标志的情况下从lib中使用category。他只是在category h/m文件中添加空的虚拟类接口/实现来强制链接器使用这个文件。是的,这个技巧可以完成这项工作。
但是作者也说他甚至没有示例化dummy对象。嗯……正如我发现的,我们应该显式地从类别文件中调用一些“真实的”的代码。所以至少应该调用类函数。我们甚至不需要dummy类。单个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

44u64gxh

44u64gxh2#

弗拉基米尔的回答其实很不错,不过,我想在这里给予更多的背景知识。也许有一天有人发现我的回复,可能会觉得很有帮助。
编译器将源文件(.c、.cc、.cpp、.m)转换为目标文件(.o)。每个源文件都有一个目标文件。目标文件包含符号、代码和数据。目标文件不能被操作系统直接使用。
现在,当构建动态库(.dylib)、框架、可加载包(.bundle)或可执行二进制文件时,这些目标文件通过链接器链接在一起,以产生操作系统认为“可用”的东西,例如,它可以直接加载到特定内存地址的东西。
然而,在构建静态库时,所有这些对象文件都被简单地添加到一个大的归档文件中,因此扩展了静态库(.a表示归档)。因此,.a文件只不过是对象的归档(.o)文件。想想没有压缩的TAR存档或ZIP存档。复制单个.a文件比复制一大堆.o文件更容易(类似于Java,将.class文件打包到.jar存档中以便于分发)。
将二进制文件链接到静态库时(= archive),链接器将获取存档中所有符号的表,并检查这些符号中哪些被二进制文件引用。只有包含引用符号的目标文件才被链接器实际加载,并被链接过程考虑。例如,如果您的存档有50个目标文件,但只有20个包含二进制文件使用的符号,只有那些20个被链接器加载,其他30个在链接过程中被完全忽略。
这对于C和C代码来说相当有效,因为这些语言试图在编译时尽可能多地执行(尽管C也有一些只在运行时使用的特性)。Obj-C是一种不同的语言。Obj-C严重依赖于运行时特性,许多Obj-C特性实际上只是运行时特性。Obj-C类实际上具有类似于C函数或全局C变量的符号(至少在当前的Obj-C运行时)。链接器可以查看类是否被引用,因此它可以确定类是否正在使用。如果您使用静态库中的对象文件中的类,则此对象文件将由链接器加载,因为链接器看到正在使用的符号。类别是仅运行时特性,类别不像类或函数那样是符号,这也意味着链接器无法确定类别是否在使用中。
如果链接器加载了一个包含Obj-C代码的目标文件,它的所有Obj-C部分都是链接阶段的一部分。(无论是类、函数还是全局变量),类别也被加载并且将在运行时可用。然而,如果对象文件本身没有被加载,其中的类别在运行时将不可用。只包含类别的对象文件永远不会加载,因为它不包含链接器会永远认为“使用中”的符号。这就是这里的全部问题。
已经提出了几种解决方案,现在您已经知道所有这些如何一起发挥作用,让我们再看看所提出的解决方案:
1.一个解决方案是将-all_load添加到链接器调用中。链接器标志实际上会做什么?实际上它告诉链接器以下内容“* 加载所有存档的所有对象文件,无论您是否看到任何正在使用的符号 ”。当然,这将起作用;但它也可能产生相当大的二进制。
1.另一种解决方案是将-force_load添加到链接器调用中,包括归档文件的路径。此标志的工作方式与-all_load完全相同,但仅适用于指定的归档文件。当然,这也会起作用。
1.最流行的解决方案是将-ObjC添加到链接器调用中。链接器标志实际上会做什么?该标志告诉链接器“
如果您看到所有归档文件包含任何Obj-C代码,则加载所有对象文件 *”。“任何Obj-C代码”包括类别。这也会起作用,并且不会强制加载不包含Obj-C代码的对象文件(这些仍然仅按需加载)。
1.另一个解决方案是相当新的Xcode构建设置Perform Single-Object Prelink。这个设置会做什么?如果启用,所有的对象文件(记住,每个源文件有一个)合并到一个目标文件中(这不是真实的的链接,因此命名为 PreLink)和此单个对象文件(有时也称为“主对象文件”)然后被添加到存档中。如果现在主对象文件的任何符号被认为在使用中,整个主对象文件被认为是在使用中,因此它的所有Objective-C部分总是被加载。由于类是普通的符号,因此使用这样一个静态库中的单个类就足以获得所有类别。

1.最终的解决方案是弗拉基米尔在他的答案最后添加的技巧。在任何只声明类别的源文件中放置“fake symbol”。如果你想在运行时使用任何类别,请确保在编译时引用 fake symbol,因为这会导致对象文件被链接器加载,因此也会加载其中的所有Obj-C代码。例如,它可能是一个函数体为空的函数(在被调用时不做任何事情),也可以是访问的全局变量(例如,一旦读取或一旦写入全局int,这就足够了)。此解决方案将有关哪些类别在运行时可用的控制转移到编译代码(如果它希望它们被链接并且可用,则它访问符号,否则它不访问符号并且链接器将忽略它)。
就这些了。
等等还有一件事
链接器有一个名为-dead_strip的选项。这个选项有什么作用?如果链接器决定加载一个目标文件,那么目标文件的所有符号都将成为链接的二进制文件的一部分,无论它们是否被使用。例如,一个目标文件包含100个函数,但只有其中一个被二进制文件使用。所有100个函数仍然被添加到二进制文件中,因为目标文件要么作为一个整体添加,要么根本不添加。2链接器通常不支持部分添加目标文件。
但是,如果你告诉链接器“死条”,链接器将首先把所有的目标文件添加到二进制文件中,解析所有的引用,最后扫描二进制文件中没有使用的符号(或仅由未使用的其他符号使用)。然后,作为优化阶段的一部分,移除所有发现未使用的符号。在上面的示例中,如果你使用像-load_all-force_loadPerform Single-Object Prelink这样的选项,这是非常有用的,因为在某些情况下,这些选项可以很容易地大幅增加二进制文件的大小,并且死剥离将再次删除未使用的代码和数据。
Dead stripping对于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添加到最后一个链接调用中,链接器将在构建日志中打印出它从哪个库中加载了哪个对象文件,因为使用了哪个符号。它将只打印第一个考虑使用的符号,但这不一定是该对象文件中唯一使用的符号。

vmdwslir

vmdwslir3#

这个问题是fixed in LLVM。修复程序是LLVM 2.9的一部分。第一个包含修复程序的Xcode版本是LLVM 3.0附带的Xcode 4.2。使用XCode 4.2时不再需要使用-all_load-force_load-ObjC仍然需要。

chhkpiq4

chhkpiq44#

以下是在编译静态库时需要做的事情,以完全解决此问题:
在Xcode Build Settings中,将Perform Single-Object Prelink设置为YES或GENERATE_MASTER_OBJECT_FILE = YES
默认情况下,链接器会为每个.m文件生成一个.o文件。所以categories会得到不同的.o文件。当链接器查看静态库.o文件时,它不会为每个类创建所有符号的索引(运行时会,不管是什么)。
这个指令将要求链接器将所有对象打包到一个大的.o文件中,并通过这个指令强制处理静态库的链接器获取所有类别的索引。
希望这能澄清它。

hi3rlvi2

hi3rlvi25#

每当讨论静态库链接时,很少提到的一个因素是,您还必须在构建阶段包括类别本身-〉复制文件并编译静态库本身的源代码
苹果在他们最近发布的Using Static Libraries in iOS中也没有强调这一事实。
我花了一整天的时间尝试各种-objC和-all_load等变量,但没有任何结果.. this问题引起了我的注意。(不要误会我的意思..你仍然需要做-objC的东西..但它不仅仅是这样)。
还有一个一直对我有帮助的动作是,我总是首先构建包含的静态库,然后构建封闭的应用程序。

ws51t4hk

ws51t4hk6#

您可能需要在静态库的“public”头中包含该类别:#import“MyStaticLib.h”

相关问题