库(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
后,符号表内将没有此符号