TL;DR
使用动态框架,您不必太在意它,因为链接器使用合理的默认行为。如果您真的想按照您的要求做,您可以指示链接器这样做,但有运行时失败的风险。请参阅答案末尾的解释。
一般来说,关于静态链接
这是经典“依赖地狱”问题的另一个版本。理论上,静态链接目标文件有两种解决方案:
-
说明您的依赖关系,并让您的框架的用户在他们的构建系统中解决它。一些想法:
CocoaPods 和 Carthage 等外部系统将帮助您解决强制约束用户构建系统的不利因素(即用户可能不使用 CocoaPods)。
包括依赖项及其头文件,指示您的用户不要使用您提供的该依赖项的版本。当然,缺点是您的用户无法根据需要切换实现。
(也许是最简单的)。构建你的框架的两个版本,一个没有链接依赖库。然后用户可以选择是使用你提供的版本还是他们自己的版本。缺点是没有很好的方法来确定它们的版本是否与您的代码兼容。
-
避免泄漏您的依赖项并将其封装在您的框架中,以牺牲更大的代码大小为代价。这通常是我现在的首选路径,因为即使在移动设备上代码大小也不是真正的问题。方法包括:
- 将依赖项作为源文件包含在您的项目中。使用
#define 宏重命名符号(或仅使用 static 符号)。
- 从源代码构建依赖项,在代码预构建(或一次性)步骤中重命名符号。
- 按原样构建所有内容,然后重命名构建库中的“内部”符号。这可能会导致错误,特别是因为 swift/obj-c 具有动态方面。例如,请参阅 this question。
This post 讨论了不同替代方案的一些优缺点。
使用动态框架时
如果您要构建动态框架,最佳做法是执行“2”。多于。具体来说,动态链接将防止重复符号问题,因为您的库可以链接到其版本的第三方库,而不管任何客户端使用的库。
这是一个简单的例子(为了简单起见,使用 C,但应该适用于 Swift、ObjC、C++ 或任何使用 ld 链接的东西):
还请注意,我假设您的第三方库是用 C/objC/C++ 编写的,因为 Swift 类 (AFAIK) 不能存在于静态库中。
myapp.c
#include <stdio.h>
void main() {
printf("Hello from app\n");
third_party_func();
my_dylib_func();
}
mylib.c
#include <stdio.h>
void my_dylib_func() {
printf("Now in dylib\n");
third_party_func();
printf("Now exiting dylib\n");
}
thirdparty_v1.c
#include <stdio.h>
void third_party_func() {
printf("Third party func, v1\n");
}
thirdparty_v2.c
#include <stdio.h>
void third_party_func() {
printf("Third party func, v2\n");
}
现在,让我们先编译文件:
$ clang -c *.c
$ ls *.o
myapp.o mylib.o thirdparty_v1.o thirdparty_v2.o
接下来,生成静态库和动态库
$ ar -rcs libmystatic.a mylib.o thirdparty_v1.o
$ ld -dylib mylib.o thirdparty_v1.o -lc -o libmydynamic.dylib
$ ls libmy* | xargs file
libmydynamic.dylib: Mach-O 64-bit dynamically linked shared library x86_64
libmystatic.a: current ar archive random library
现在,如果我们使用(隐式)提供的实现进行静态编译:
clang -omyapp myapp.o -L. -lmystatic thirdparty_v2.o && ./myapp
Hello from app
Third party func, v1
Now in dylib
Third party func, v1
Now exiting dylib
现在,这让我感到非常惊讶,因为我预计会出现“重复符号”错误。事实证明,OSX 上的ld 默默地替换了符号,导致用户的应用程序替换了我库中的符号。原因记录在 ld 手册页中:
ld 只会在需要解析某些符号引用时从静态库中提取 .o 文件
这将对应于上面的第 1 点。 (附带说明,在 Linux 上运行上述示例肯定会出现“重复符号”错误)。
现在,让我们像您的示例一样动态链接:
clang -omyapp myapp.o -L. -lmydynamic thirdparty_v2.o && ./myapp
Hello from app
Third party func, v2
Now in dylib
Third party func, v1
Now exiting dylib
如您所见,您的动态库现在引用静态库的版本 (v1),而应用程序本身将使用另一个 (v2) 版本。 这可能是您想要的,这是默认设置。原因当然是现在有两个二进制文件,每个都有自己的一组符号。如果我们检查.dylib,我们可以看到它仍然导出第三方库:
$ nm libmydynamic.dylib
0000000000000ec0 T _my_dylib_func
U _printf
0000000000000f00 T _third_party_func
U dyld_stub_binder
当然,如果我们不链接到应用程序中的静态库,链接器会在 .dylib 中找到符号:
$ clang -omyapp myapp.o -L. -lmydynamic && ./myapp
Hello from app
Third party func, v1
Now in dylib
Third party func, v1
Now exiting dylib
现在,如果我们不想向使用某个静态库的应用程序公开,我们可以通过不导出其符号来隐藏它(隐藏它是为了不让应用程序意外引用它,而不是真正隐藏它) :
$ ld -dylib mylib.o -unexported_symbol '_third_party_*' thirdparty_v1.o -lc -o libmydynamic_no3p.dylib
$ nm -A libmydyn*
...
libmydynamic.dylib: 0000000000000f00 T _third_party_func
libmydynamic_no3p.dylib: 0000000000000f00 t _third_party_func
(大写的T表示符号是公开的,小写的t表示符号是私有的)。
让我们再试试最后一个例子:
$ clang -omyapp myapp.o -L. -lmydynamic_no3p && ./myapp
Undefined symbols for architecture x86_64:
"_third_party_func", referenced from:
_main in myapp.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
现在我们已经成功地将我们的第三方静态框架隐藏到客户端应用程序中。请注意,通常您不必在意。
如果你真的想要 1. 在动态框架中怎么办
例如,您的库可能需要您的客户端提供的第三方库的确切版本。
当然也有一个链接器标志:-undefined dynamic_lookup。
$ ld -dylib mylib.o -undefined dynamic -lc -o libmydynamic_undef.dylib
$ clang -omyapp myapp.o -L. -lmydynamic_undef thirdparty_v2.o && ./myapp
Hello from app
Third party func, v2
Now in dylib
Third party func, v2
Now exiting dylib
当然,如果您的客户端未能包含静态库,它会在运行时失败。