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信息

image.png

打印Mach header : otool -h

打印text segment,text section otool -t

TEXT段:只读区域,包含可执行代码和常量数据
DATA段:读/写,包含初始化和未初始化数据和一些动态链接专属数据

  • 工作区间 : workspace
    • 可重用性。多个模块可以在多个项目中使用,节约开发时间和维护时间
    • 节约测试时间。单独模块意味着每个模块中都可以添加测试功能
    • 更好的立即模块化思想。
  • 查看命令帮助 ```bash //方式一: man nm

//方式二: nm —help

//搜索 -p /-p

//跳到下一个匹配项 n

//跳到上一个匹配项 N

//退出 q

  1. > 在同一文件中定义两个全局符号,一个赋值初始值,另一个不赋值初始化,不会报错:(在编译过程中,未定义的全局符号,找到同名的全局符号后,会删除未定义的全局符号。在链接过程中,未定的全局符号会强制为已定义的全局符号),
  2. 查看文件的具体格式
  3. ```bash
  4. file <file-path>

2. 手动命令链接静态库 test.m —> test.o 导入头文件AFNetworking

静态库是 .o文件的合集

用ar命令查看静态库中的目标文件

  1. //ar 压缩目标文件,并对其进行编号和索引,形成静态库。同时也可以解压缩静态库,查看有哪些目标文件:
  2. // -r: a.a添加or替换文件
  3. // -c: 不输出任何信息
  4. // -t: 列出包含的目标文件
  5. ar -t libAFNetworking.a

image.png

1) 需要编译链接的源文件 test.m

  1. #import <Foundation/Foundation.h>
  2. #import <AFNetworking.h>
  3. int main(){
  4. AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
  5. NSLog(@"testApp----%@", manager);
  6. return 0;
  7. }

2)查看clang命令 man clang

image.png

clang 是汇编器+链接器

clang命令参数:
-x: 指定编译文件语言类型
-g: 生成调试信息
-c: 生成目标文件,只运行preprocess,compile,assemble,不链接
-o: 输出文件
-isysroot: 使用的SDK路径
1. -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

3) 静态库是.o文件的合集

image.png

  1. clang -x objective-c \
  2. -target x86_64-apple-macos10.15 \
  3. -fobjc-arc \
  4. -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
  5. -c TestExample.m -o TestExample.o

TestExample.o 重命名 libTestExample.dylib -> libTestExample

老系统TestExample.o直接改成libTestExample.a

  1. //libTestExample.dylib: Mach-O 64-bit object x86_64
  2. file libTestExample.dylib
  3. //libTestExample.a: Mach-O 64-bit object x86_64
  4. file libTestExample.a


4) 编译test.m生成目标文件 test.o,test.o链接 链接libTestExample生成可执行文件

  • 目录结构:

image.png

  • test.m源码 ```objectivec

    import

    import “TestExample.h”

int main(){ NSLog(@”testApp——“); TestExample *manager = [TestExample new]; [manager lg_test: nil]; return 0; }

  1. - 编译test.m 生成目标文件 test.o
  2. /**<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 /> */
  3. ```bash
  4. clang -x objective-c \
  5. -target x86_64-apple-macos10.15 \
  6. -fobjc-arc \
  7. -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
  8. -I./StaticLibrary \
  9. -c test.m -o test.o
  • otool -l test.o

image.png

  • test.o链接libTestExample.a生成test可执行文件

    -L./StaticLibrary 在当前目录的子目录StaticLibrary查找需要的库文件
    -lTestExample 链接的名称为libTestExample/TestExample的动态库或者静态库
    查找规则:先找lib+的动态库,找不到,再去找lib+的静态库,还找不到,就报错
    */

    1. clang -target x86_64-apple-macos10.15 \
    2. -fobjc-arc \
    3. -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
    4. -L./StaticLibrary \
    5. -lTestExample \
    6. test.o -o test
  • 终端输入lldb 进入lldb

file : 将文件包装成一个target
r : 运行
image.png

  • 整个过程用脚本实现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

  1. - 执行权限修改
  2. ![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)
  3. ```bash
  4. man objdump

image.png
查看mach header

  1. objdump --macho --private-header libTestExample.a

image.png

5) 静态库合并(两个.a文件合并为一个.a文件)

man libtool
image.png文档目录结构
image.png
ar`压缩目标文件,并对其进行编号和索引,形成静态库。同时也可以解压缩静态库,查看有哪些目标文件:
ar -rc a.a a.o
-r: 向a.a添加or替换文件
-c: 不输出任何信息
-t: 列出包含的目标文件

  1. ar -rc libAFNetworking.a libSDWebImage.a

⚠️ mudule 预先把.h文件编译,缓存到目录中,多处导入同一个文件时,直接拿过来用

6) Auto-link

。启用这个特性后,当我们 import <模块> ,不需要我们再去往链接器去配置链接参数。比如 import <framework> 我们在代码里使用这个是framework格式的库文件,那么在生成目标文件时,会自动在目标文件的 Mach-O 中,插入一个 load command 格式是 LC_LINKER_OPTION ,存储这样一个链接器参数 -framework <framework>
image.png

7) swiftc编译

swiftc —help —-> swiftc —help | grep — ‘-D’
image.png

8) 手动链接framework

  1. -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的静态库,还找不到,就报错
  • 查看结构::

image.png

  • test.m-> test.o

test.m

  1. #import <Foundation/Foundation.h>
  2. #import "TestExample.h"
  3. int main(){
  4. NSLog(@"testApp----");
  5. TestExample *manager = [TestExample new];
  6. [manager lg_test: nil];
  7. return 0;
  8. }

编译test.m生成目标文件test.o

  1. clang -x objective-c -fmodules \
  2. -target x86_64-apple-macos10.15 \
  3. -fobjc-arc \
  4. -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
  5. -I./Frameworks/TestExample.framework/Headers \
  6. -c test.m -o test.o
  • test.o链接TestExample.framework生成test可执行文件
    1. clang -target x86_64-apple-macos10.15 \
    2. -fobjc-arc \
    3. -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk \
    4. -F./Frameworks \
    5. -framework TestExample \
    6. test.o -o test

9) dead code strip

  • 针对链接静态库时,进行死代码删除的可选参数:-all_load,-noall_load, -Objc, -force_load<file>
  • 链接过程,链接器给我们提供的优化方式 Dead Code Stripping

截屏2021-03-02 下午7.21.32.png
在LGStaticFramework.framework中提供接口- (void)lg_test;
- (void)lg_test调用其分类的方法,实现如下:

  1. - (void)lg_test {
  2. // dead_strip
  3. [self lg_test_category];
  4. }

会产生问题: 分类,在运行时创建 在链接过程中,发现分类没使用,就好将分类干掉

LGStaticFramework.framework的源码放入工程一同进行编译:

  • LGStaticFramework.framework源码工程创建工作区

File -> Save as worksapace ->输入工作区名称TestStaticFrameworkimage.png

  • Add Files to “LGStaticFramework”—>添加LLGApp.xcodeproj工程

image.png

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

image.png

  • 将静态库Embed到LGApp工程中

image.png

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

image.png

查看代码段TEXTtext objdump —macho -d test

解决方式一:
image.png

解决方式二:
image.png

解决方式三:
image.png

⚠️ 以上三种方式,只适用控制链接器去链接静态库时,进行死代码删除的选项 Build Setting中的dead code stripping是在源码编译链接过程中的链接器给我们的一种优化方式,死代码删除

  • test.m 源码 ```objectivec

    import

    import “TestExample.h”

    // 全局 void global_function() {

} int main(){ global_function(); NSLog(@”testApp——“); // TestExample *manager = [TestExample new]; // [manager lg_test: nil]; return 0; }

// 本地 static void static_function() {

}

  1. - 脚本源码 build.sh
  2. ```bash
  3. SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk
  4. FILE_NAME=test
  5. HEADER_SEARCH_PATH=./StaticLibrary
  6. echo "-----开始编译test.m"
  7. clang -x objective-c \
  8. -target x86_64-apple-macos10.15 \
  9. -fobjc-arc \
  10. -isysroot $SYSROOT \
  11. -I${HEADER_SEARCH_PATH} \
  12. -c ${FILE_NAME}.m -o ${FILE_NAME}.o
  13. echo "-----开始进入StaticLibrary"
  14. pushd ./StaticLibrary
  15. clang -x objective-c \
  16. -target x86_64-apple-macos10.15 \
  17. -fobjc-arc \
  18. -isysroot $SYSROOT \
  19. -c TestExample.m -o TestExample.o
  20. ar -rc libTestExample.a TestExample.o
  21. echo "-----开始退出StaticLibrary"
  22. popd
  23. echo "-----开始test.o to test EXEC"
  24. clang -target x86_64-apple-macos10.15 \
  25. -fobjc-arc \
  26. -isysroot $SYSROOT \
  27. -L./StaticLibrary \
  28. -lTestExample \
  29. ${FILE_NAME}.o -o ${FILE_NAME}
  • 执行脚本后,查看代码段

    1. objdump --macho -d test

    image.png
    没有静态库中的lg_test的符号,原因:链接静态库,链接器默认是 -noall_load ,没有使用静态库,静态库中的代码不会存放到可执行文件中

  • 给链接器传参数 -Xlinker -all_load -Xlinker -why_live -Xlinker _global_function ,重新编译后查看符号 objdump --macho -syms test

image.png

  • 给链接器传参数 -Xlinker -dead_strip

-dead_strip 作用是没有被入口点或导出符号用到的函数和代码会被删除
image.png
当全局符号没有被入口点使用时,会被编译器的-dead_strip干掉,test可执行文件中不会含有该符号
image.png
只有被入口点使用或者导出符号使用到,才不会被-dead_strip干掉
image.png

10) .o +.o -> .o

把所有的.o合并成一个大的.o之后,然后再进行链接
链接时的优化 Link-Time Optimization
image.png

11).o 使用 .a静态库

在链接静态库时,默认是-noall_load会将没有使用的静态库代码干掉后再组合成.o,我们可以通过设置链接器参数 -Xlinker -all_load或者-Xlinker -force_load [静态库路径】保留静态库中所有符号,OC编码的静态库中还可以使用-ObjC来保留静态库中的符号(因为OC是动态语言,在运行时才确定是否调用,所以编译器不敢在编译阶段就将符号干掉)