1. 概念
- 库文件格式:
. a 、.dylib 、 .framework 、 .xcframework
- 什么是库
一段 编译好的二进制代码 , 加上头文件 就可以供别人使用
- 什么时候用到库
代码提供给别使用,但 不希望别人看到具体实现的源码 ,就可以封装成库,只暴露头文件
对某些 不会进行大的改动的代码 ,想 减少编译的时间 ,库是编译好的二进制,只要 链接 ,不占用编译时间
- 库在使用的时候需要链接(Link),链接的方式有两种:
静态链接,动态链接 - 什么是静态库:
header + .a +签名+资源文件`
静态库: 静态链接库 ,可看成是 一组目标文件的集合
Windows下的 .lib
Linux下的 .a
Mac下的 .a 、 .framework
静态库的某个目标文件中的代码没有在项目中引用,那么就不会链接到项目的Mach-o文件内。静态库默认仅将用到的文件链接进去, 以类文件为最小链接单位
静态库的缺点: 浪费内存和磁盘空间,模块更新困难
- 什么是动态库:
header + .dylib + 签名 + 资源文件
动态库在编译时不会拷贝到目标程序中,目标程序中只存储指向动态库的引用。等到运行时,动态库才会被真正加载进来,格式有: .framework 、 .dylib 、 .tdb
缺点: 会导致一些性能损失,但可优化,比如延迟绑定(Lazy Binding)技术
- 什么是tdb(text-based stub libraries)格式
本质上就是 一个YAML描述的文本文件
作用: 记录动态库的一些信息 ,包括导出的符号、动态库的架构信息、动态库的依赖信息。用于 避免在真机开发过程中直接使用传统的dylib
对于真机来说,由于动态库都是在设备上,在Xcode上使用基于tdb格式的伪framework可以大大减小Xcode的大小
- Framework
Mac OS/iOS平台可以使用Framework,Framework是一种打包方式,将库的二进制文件,头文件和有关的资源文件打包到一起,方便管理和分发
Framework和系统的UIKit.framework的区别:
系统的Framework不需要拷贝到目标程序中,我们自己的Framework虽是动态库,最后还是要拷贝到APP中。因此苹果把自己定义的framework成为 Embedded Framework
- XCFramework:是苹果官方推荐的,支持的,可以更方便的表示一个多个平台架构的分发二进制的格式。需要
Xcode11以上支持。是为更好的支持Mac Catalyst和ARM芯片的macOS。专门在2019年提出的Framework的另一种先进格式。 - 不同平台的架构
ios/ipad:ARM64
ios/iPad Simulator: x86_64, arm64(M1的电脑)
Mac Catalyst:x86_64, arm64(M1的电脑)
Mac: x86_64, arm64(M1的电脑)
- Embedded Framework
开发中使用的动态库会被放入到 ipa下的Framework目录下 ,基于沙盒运行。
不同的APP使用相同的动态库,并不会只在系统中存在一份,而是会在多个APP中各自打包、签名、加载一份
- Mach-o File Format
一个Mach-o文件有两部分组成:header和data
header:包含三种类型。Macho header 、 segment、 sections。
- segment(segement commands):指定操作系统应该是
将Segments加载到内存中的什么位置,以及为该Segments分配多少字节数。还指定文件中的哪些字节属于该Segments,以及文件包含多少sections.Mac上始终是4096字节或者4KB的倍数,其中4096字节是最小大小。iOS上是8KB。Segments名称的约定是使用全大写字母,后跟双下划线(eg:__TEXT). - sections
描述了对应的二进制信息,所有sections都在每个segment之后一个接一个地描述。sections里面定义其名称,在内存中的地址、大小,文件中section数据的偏移量和segment名称。Section的名称约定是使用全小写字母,再加上双下划线(eg:__text). - Mach header属于header的一部分,它包含了整个文件的信息和segment信息

打印Mach header : otool -h
打印text segment,text section otool -t
TEXT段:只读区域,包含可执行代码和常量数据
DATA段:读/写,包含初始化和未初始化数据和一些动态链接专属数据
- 工作区间 : workspace
- 可重用性。多个模块可以在多个项目中使用,节约开发时间和维护时间
- 节约测试时间。单独模块意味着每个模块中都可以添加测试功能
- 更好的立即模块化思想。
- 查看命令帮助 ```bash //方式一: man nm
//方式二: nm —help
//搜索 -p /-p
//跳到下一个匹配项 n
//跳到上一个匹配项 N
//退出 q
> 在同一文件中定义两个全局符号,一个赋值初始值,另一个不赋值初始化,不会报错:(在编译过程中,未定义的全局符号,找到同名的全局符号后,会删除未定义的全局符号。在链接过程中,未定的全局符号会强制为已定义的全局符号),查看文件的具体格式```bashfile <file-path>
2. 手动命令链接静态库 test.m —> test.o 导入头文件AFNetworking
静态库是 .o文件的合集
用ar命令查看静态库中的目标文件
//ar 压缩目标文件,并对其进行编号和索引,形成静态库。同时也可以解压缩静态库,查看有哪些目标文件:// -r: 向a.a添加or替换文件// -c: 不输出任何信息// -t: 列出包含的目标文件ar -t libAFNetworking.a
1) 需要编译链接的源文件 test.m
#import <Foundation/Foundation.h>#import <AFNetworking.h>int main(){AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];NSLog(@"testApp----%@", manager);return 0;}
2)查看clang命令 man clang

clang 是汇编器+链接器
clang命令参数:
-x: 指定编译文件语言类型
-g: 生成调试信息
-c: 生成目标文件,只运行preprocess,compile,assemble,不链接
-o: 输出文件
-isysroot: 使用的SDK路径
1. -I
2. -L
3. -l
-F
-framework
3) 静态库是.o文件的合集

clang -x objective-c \-target x86_64-apple-macos10.15 \-fobjc-arc \-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \-c TestExample.m -o TestExample.o
TestExample.o 重命名 libTestExample.dylib -> libTestExample
老系统TestExample.o直接改成libTestExample.a
//libTestExample.dylib: Mach-O 64-bit object x86_64file libTestExample.dylib//libTestExample.a: Mach-O 64-bit object x86_64file libTestExample.a
4) 编译test.m生成目标文件 test.o,test.o链接 链接libTestExample生成可执行文件
- 目录结构:

int main(){ NSLog(@”testApp——“); TestExample *manager = [TestExample new]; [manager lg_test: nil]; return 0; }
- 编译test.m 生成目标文件 test.o/**<br /> 将test.m编译成test.o:<br /> 1. 使用OC<br /> 2. 生成的是X86_64_macOS架构的代码<br /> Big Sur是:x86_64-apple-macos10.15,之前是:x86_64-apple-macos10.15<br /> 3. 使用ARC<br /> 4. 使用的SDK的路径在:<br /> Big Sur是:/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk<br /> 之前是:/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk<br /> 5. 用到的其他库的头文件地址在./Frameworks<br /> */```bashclang -x objective-c \-target x86_64-apple-macos10.15 \-fobjc-arc \-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \-I./StaticLibrary \-c test.m -o test.o
- otool -l test.o

test.o链接libTestExample.a生成test可执行文件
-L./StaticLibrary 在当前目录的子目录StaticLibrary查找需要的库文件
-lTestExample 链接的名称为libTestExample/TestExample的动态库或者静态库
查找规则:先找lib+的动态库,找不到,再去找lib+ 的静态库,还找不到,就报错
*/clang -target x86_64-apple-macos10.15 \-fobjc-arc \-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \-L./StaticLibrary \-lTestExample \test.o -o test
终端输入lldb 进入lldb
file
r : 运行
- 整个过程用脚本实现build.sh ```bash LANGUAGE=objective-c TAREGT=x86_64-apple-macos10.15 SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk
FILE_NAME=test STATICLIBRARY=TestExample HEAD_PATH=./StaticLibrary LIBRARY_PATH=./StaticLibrary
echo “——————-编译test.m to test.o—————————“ clang -x $LANGUAGE \ -target $TAREGT \ -fobjc-arc \ -isysroot $SYSROOT \ -I${HEAD_PATH} \ -c ${FILE_NAME}.m -o ${FILE_NAME}.o
echo “——————-进入到StaticLibrary目录—————————“ pushd ${HEAD_PATH} echo “——————-编译TestExample.m to TestExample.o—————————“ clang -x $LANGUAGE \ -target $TAREGT \ -fobjc-arc \ -isysroot $SYSROOT \ -c ${STATICLIBRARY}.m -o ${STATICLIBRARY}.o echo “——————-退出StaticLibrary目录—————————“
popd
echo “——————-test.o链接libTestExample.a to test EXEC—————————“ clang -target $TAREGT \ -fobjc-arc \ -isysroot $SYSROOT \ -L${LIBRARY_PATH} \ -l${STATICLIBRARY} \ $FILE_NAME.o -o $FILE_NAME
- 执行权限修改```bashman objdump

查看mach header
objdump --macho --private-header libTestExample.a

5) 静态库合并(两个.a文件合并为一个.a文件)
man libtool
文档目录结构
ar`压缩目标文件,并对其进行编号和索引,形成静态库。同时也可以解压缩静态库,查看有哪些目标文件:
ar -rc a.a a.o
-r: 向a.a添加or替换文件
-c: 不输出任何信息
-t: 列出包含的目标文件
ar -rc libAFNetworking.a libSDWebImage.a
⚠️ mudule 预先把.h文件编译,缓存到目录中,多处导入同一个文件时,直接拿过来用
6) Auto-link
。启用这个特性后,当我们 import <模块> ,不需要我们再去往链接器去配置链接参数。比如 import <framework> 我们在代码里使用这个是framework格式的库文件,那么在生成目标文件时,会自动在目标文件的 Mach-O 中,插入一个 load command 格式是 LC_LINKER_OPTION ,存储这样一个链接器参数 -framework <framework> 。
7) swiftc编译
swiftc —help —-> swiftc —help | grep — ‘-D’
8) 手动链接framework
- -I
在指定目录寻找头文件 header search path
2. -L指定库文件路径(.a.dylib库文件) library search path
3. -l指定链接的库文件名称(.a.dylib库文件)other link flags -lAFNetworking
-F在指定目录及子目录寻找framework framework search path
-framework指定链接的framework名称 other link flags -framework AFNetworking
查找规则:先找TestExample.framework的动态库,找不到,再去找TestExample.framework的静态库,还找不到,就报错
- 查看结构::

- test.m-> test.o
test.m
#import <Foundation/Foundation.h>#import "TestExample.h"int main(){NSLog(@"testApp----");TestExample *manager = [TestExample new];[manager lg_test: nil];return 0;}
编译test.m生成目标文件test.o
clang -x objective-c -fmodules \-target x86_64-apple-macos10.15 \-fobjc-arc \-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \-I./Frameworks/TestExample.framework/Headers \-c test.m -o test.o
- test.o链接TestExample.framework生成test可执行文件
clang -target x86_64-apple-macos10.15 \-fobjc-arc \-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \-F./Frameworks \-framework TestExample \test.o -o test
9) dead code strip
- 针对链接静态库时,进行死代码删除的可选参数:
-all_load,-noall_load, -Objc, -force_load<file> - 链接过程,链接器给我们提供的优化方式
Dead Code Stripping

在LGStaticFramework.framework中提供接口- (void)lg_test;
- (void)lg_test调用其分类的方法,实现如下:
- (void)lg_test {// dead_strip[self lg_test_category];}
会产生问题: 分类,在运行时创建 在链接过程中,发现分类没使用,就好将分类干掉
LGStaticFramework.framework的源码放入工程一同进行编译:
- LGStaticFramework.framework源码工程创建工作区
File -> Save as worksapace ->输入工作区名称TestStaticFramework
- Add Files to “LGStaticFramework”—>添加LLGApp.xcodeproj工程

- 添加后LGApp为红色,需要关闭工程,打开TestStaticFramework.xcworkspace

- 将静态库Embed到LGApp工程中

- 编译报错—> 原因静态库在编译链接时,链接器默认是 -noall_load,没有适用的符号不会加载进来

查看代码段TEXTtext objdump —macho -d test
解决方式一:
解决方式二:
解决方式三:
⚠️ 以上三种方式,只适用控制链接器去链接静态库时,进行死代码删除的选项 Build Setting中的dead code stripping是在源码编译链接过程中的链接器给我们的一种优化方式,死代码删除
} int main(){ global_function(); NSLog(@”testApp——“); // TestExample *manager = [TestExample new]; // [manager lg_test: nil]; return 0; }
// 本地 static void static_function() {
}
- 脚本源码 build.sh```bashSYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdkFILE_NAME=testHEADER_SEARCH_PATH=./StaticLibraryecho "-----开始编译test.m"clang -x objective-c \-target x86_64-apple-macos10.15 \-fobjc-arc \-isysroot $SYSROOT \-I${HEADER_SEARCH_PATH} \-c ${FILE_NAME}.m -o ${FILE_NAME}.oecho "-----开始进入StaticLibrary"pushd ./StaticLibraryclang -x objective-c \-target x86_64-apple-macos10.15 \-fobjc-arc \-isysroot $SYSROOT \-c TestExample.m -o TestExample.oar -rc libTestExample.a TestExample.oecho "-----开始退出StaticLibrary"popdecho "-----开始test.o to test EXEC"clang -target x86_64-apple-macos10.15 \-fobjc-arc \-isysroot $SYSROOT \-L./StaticLibrary \-lTestExample \${FILE_NAME}.o -o ${FILE_NAME}
执行脚本后,查看代码段
objdump --macho -d test

没有静态库中的lg_test的符号,原因:链接静态库,链接器默认是-noall_load,没有使用静态库,静态库中的代码不会存放到可执行文件中给链接器传参数
-Xlinker -all_load-Xlinker -why_live -Xlinker _global_function,重新编译后查看符号objdump --macho -syms test

- 给链接器传参数
-Xlinker -dead_strip
-dead_strip 作用是没有被入口点或导出符号用到的函数和代码会被删除
当全局符号没有被入口点使用时,会被编译器的-dead_strip干掉,test可执行文件中不会含有该符号
只有被入口点使用或者导出符号使用到,才不会被-dead_strip干掉
10) .o +.o -> .o
把所有的.o合并成一个大的.o之后,然后再进行链接
链接时的优化 Link-Time Optimization 
11).o 使用 .a静态库
在链接静态库时,默认是-noall_load会将没有使用的静态库代码干掉后再组合成.o,我们可以通过设置链接器参数 -Xlinker -all_load或者-Xlinker -force_load [静态库路径】保留静态库中所有符号,OC编码的静态库中还可以使用-ObjC来保留静态库中的符号(因为OC是动态语言,在运行时才确定是否调用,所以编译器不敢在编译阶段就将符号干掉)
