库(library)
在平常的开发过程中,我们一定有接触过库,那么到底库是什么?
通俗来讲,库就是一段编译好的二进制代码,加上头文件可供别人使用。
库的用处
- 某些代码需要给别人使用,但是又不希望别人可以看到源码,那么就以库的形式进行封装,只暴露出头文件。
- 对于某些不会进行大的改动的代码,我们想减少编译时间,就可以将它打包成库,因为库是已经编译好的二进制文件,只需要
Link,不会浪费编译时间。
Link
库在使用的时候需要链接(Link),链接的方式有两种:静态和动态。
静态库
定义:静态库即静态链接库:可以简单看做一组目标文件的集合,即很多目标文件经过压缩打包后形成的文件。例如Windows下的lib,Linux和Mac下的.a以及Mac独有的.framework。
缺点:浪费内存和磁盘空间,模块更新困难。
静态库的文件格式
静态库常见的文件格式:
.a.framework(既有动态库也有动态库)xcframework是苹果官方推荐和支持的可以更方便的表示一个多个平台和架构的分发二进制库的格式,需要Xcode11以上支持
xcframework:
xcframework和传统framework相比:
- 可以用单个
xcframework文件提供多个平台的分发二进制文件 - 与
Fat Header相比,可以按照平台划分,可以包含相同架构的不同平台文件 - 在使用时不再需要脚本去剥离不需要的架构体系
探究静态库
刚刚有说过静态库就是目标文件的集合,那么到底是这样吗?
这里就拿我们最熟悉的AFNetWorking来做分析,拿到其中的.a文件
使用命令file libAFNetworking.a查看,表明这个就是一个归档文件
刚有提到ar,通过终端来查看ar命令
使用命令ar -t libAFNetworking.a来查看其中的文件
得以验证的确静态库是一系列目标文件的合集。
链接静态库
先在.m中写出对AFNetWorking的使用,然后看看如何用clang对其进行链接
可以看到clang是针对C、C++、Objc的编译器,其中包括预处理、解析、优化、代码生成、汇编化、链接。
若要链接,首先将test.m编译成为目标文件test.o
使用clang -x objective-c \ -target arm64-apple-macos12.0.1 \ -fobjc-arc \ -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk \ -c test.m -o test.o但是发现了报错
对于头文件找不到,我们需要告诉它路径,即Header Search Path,那么对于clang来讲则是-I参数
使用命令:
clang -x objective-c \ -target arm64-apple-macos12.0.1 \ -fobjc-arc \ -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk \ -I ./AFNetworking \ -c test.m -o test.o
这时就成功生成了test.o文件
再使用命令:
clang -target arm64-apple-macos12.0.1 \ -fobjc-arc \-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk \-L./AFNetworking \-lAFNetworking \test.o -o test
链接成功,得到可执行文件
clang 指令分析
clang:
-x:指定编译文件语言类型-g:生成调试信息-c:生成目标文件,只运行preprocess、compile、assemble但是不链接-o:输出文件-I<directory>:在指定目录寻找头文件-L<dir>:指定库文件路径(.a.dylib库文件)-l<library_name>:指定链接的库文件名称(.a/.dylib库文件)-F<directory>:在指定目录寻找framework头文件-framework<framework_name>在指定的framework名称生成相应的LLVM文件格式,来进行链接时间优化,当配合使用-S使用时,生成汇编语言文件,否则生成bitcode格式的目标文件
创建并链接静态库
目标:将TestExample封装为静态库,生成test.o文件并对生成的静态库进行链接
过程:
1.将TestExample.m编译生成目标文件
clang -x objective-c \-target arm64-apple-macos12.0 \-fobjc-arc \-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk \-c TestExample.m -o TestExample.o
2.把TestExample.o改名为libTestExample.dylib后再将.dylib去掉,并使用file命令查看
3.将Test.m生成目标问价Test.o
clang -x objective-c \-target arm64-apple-macos12.0 \-fobjc-arc \-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk \-I./StaticLibrary \-c test.m -o test.o
4.链接库生成可执行文件
clang -target arm64-apple-macos12.0 \-fobjc-arc \-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk \-L./StaticLibrary \-lTestExample \test.o -o test
5.利用lldb运行可执行文件,进入lldb环境后,使用命令file test、run
合并静态库
静态库合并有两种方案,使用ar或者libtool
现在列出两个静态库文件
ar
使用ar -x libAFNetworking.a和ar -x libSDWebImage.a先将两个静态库解压出所有文件
利用ar -vr combinelib.a *.o __.SYMDEF将所有目标文件打包成combinelib.a,此时可以使用file或者ar -t命令看看合并的文件格式是否正确
libtool
先看一下libtool的文档简介
使用命令libtool -static -o combinelib.a libAFNetworking.a libSDWebImage.a
同样完成了合并静态库,显然libtool更为简便
Framework
对于MacOS和iOS平台来说还可以使用Framework。Framework实际是一种打包方式,将库的二进制文件,头文件和有关的资源文件打包在一起,方便管理和分发。系统的Framework不需要拷贝到目标程序中,而我们自己制作的Framework哪怕是动态的也需要拷贝到目标程序中,因此这种Framework又被称为Embedded Framework。
Framework组成:
静态库:Header+.a+签名+资源文件动态库:Header+.dylib+签名+资源文件- 如果在
Embedded情况下都要拷贝至目标文件
制作Framework
使用命令得到.o文件:clang -x objective-c \-target arm64-apple-macos12.0 \-fobjc-arc \-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk \-c TestExample.m -o TestExample.o
使用命令ar -rc TestExample TestExample.o
至此Header+.a均已得到
创建Framework文件夹后并在其中创建Headers文件夹,内容如下
同样现将test.m编译为目标文件后去链接framwork
clang -x objective-c \-target arm64-apple-macos12.0 \-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk \-fobjc-arc \-I./Frameworks/TestExample.framework/Headers \-c test.m -o test.o
clang -target arm64-apple-macos12.0 \-fobjc-arc \-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk \-F./Frameworks \-framework TestExample \test.o -o test
进入lldb运行,链接成功
dead code strip(死代码剥离)
如果主程序对链接的静态库中的方法并未使用,那么未使用的方法是否会出现在可执行程序的代码段?
针对不同情况可以使用objdump --macho -d来查看代码段
可以看到在注释的情况下静态库的代码不会出现在可执行程序的代码段。这样看起来好像并没有什么影响,反而是剥离了无用代码,但是如果链接的静态库中调用了分类的代码,这里会出现问题,因为dead code strip是在链接的过程中就生效了,但是分类是程序运行过程中才动态创建的。
这种情况如图所示,程序运行后会报错
那么既然是在链接过程中剥离的,可以配置参数告诉链接器如何剥离或者不剥离
默认参数时不都加载
使用全部加载
传给链接器全部加载的参数
这样即可运行成功,但是全部链接也是duck不必,只需要保留自己需要的即可
三种方法:-all_load、-Objc、-force_load
-Objc:只保留OC方法
-force_load:对指定的全部加载
注意:
-all_load、-Objc、-force_load只针对于静态库
-dead_strip是移除没有被入口或者导出符号表使用的数据和方法。
命令objdump --macho --syms查看符号表
objdump --macho --exports-trie查看导出符号表
虽然global_func未调用但是也存在
使用-dead_strip后,符号表内将没有此符号