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
> 在同一文件中定义两个全局符号,一个赋值初始值,另一个不赋值初始化,不会报错:(在编译过程中,未定义的全局符号,找到同名的全局符号后,会删除未定义的全局符号。在链接过程中,未定的全局符号会强制为已定义的全局符号),
查看文件的具体格式
```bash
file <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_64
file libTestExample.dylib
//libTestExample.a: Mach-O 64-bit object x86_64
file 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 /> */
```bash
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 \
-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
- 执行权限修改
![image.png](https://cdn.nlark.com/yuque/0/2021/png/1994311/1611244419953-04f467c1-8ea7-4169-b021-13493d4658de.png#align=left&display=inline&height=104&originHeight=208&originWidth=1446&size=387728&status=done&style=none&width=723)
```bash
man 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
```bash
SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk
FILE_NAME=test
HEADER_SEARCH_PATH=./StaticLibrary
echo "-----开始编译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}.o
echo "-----开始进入StaticLibrary"
pushd ./StaticLibrary
clang -x objective-c \
-target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot $SYSROOT \
-c TestExample.m -o TestExample.o
ar -rc libTestExample.a TestExample.o
echo "-----开始退出StaticLibrary"
popd
echo "-----开始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是动态语言,在运行时才确定是否调用,所以编译器不敢在编译阶段就将符号干掉)