静态库原理
.a:静态库.framework:既有静态库也有动态库.dylib:传统意义上的动态库.xcframework:2019年苹果推出的用于解决不同架构的库导致的开发问题
Framework 实际上是一种打包方式,将库的二进制文件,头文件和有关的资源文件打包到一起,方便管理和分发。
Framework和系统的UlKit.Framework还是有很大区别。系统的Framework不需要拷贝到目标程序中,我们自己做出来的Framework哪怕是动态的,最后也还是要拷贝到App中(App和Extension的Bundle是共享的),因此苹果又把这种Framework称为Embedded Framework。
使用场景
- 某些代码需要给别⼈使⽤,但是我们不希望别⼈看到源码,就需要以库的形式进⾏封装,只暴露出头⽂件。
- 对于某些不会进⾏⼤改动的代码,我们想减少编译的时间,就可以把它打包成库。因为库是已经编译好的⼆进制,编译的时候只需要
Link⼀下,不会浪费编译时间。
库(Library)就是一段编译好的二进制代码,加上头文件就可以供别人使用。库(Library)在使⽤的时候需要链接(Link),链接的⽅式有两种:静态和动态。静态库即静态链接库:可以简单的看成⼀组⽬标⽂件(.o文件)的集合。即很多⽬标⽂件经过压缩打包后形成的⽂件。
链接静态库
在测试项目,包含有静态库libAFNetworking.a和使用AFNetworking的头文件。

静态库.a是什么格式?
使用file命令,查看一个文件的具体格式。
chenshuangchao@chenshuhaodeMBP ~ % file /Users/chenshuangchao/Desktop/链接 静态库AFNetworking/AFNetworking/libAFNetworking.a
/Users/chenshuangchao/Desktop/链接静态库AFNetworking/AFNetworking/libAFNetworking.a: current ar archive
-
executable表示可执行的; -
a.out为可执行文件; -
shared object表示动态链接库; -
relocatable表示可重定位,即目标文件; -
current ar archive表示静态链接库。
archive是文档的意思;也就是我们常说的静态库是目标文件.O的合集。
怎么验证静态库是目标文件.O的合集?可以使用ar指令。
ar -t /Users/chenshuangchao/Desktop/链接静态库AFNetworking/AFNetworking/libAFNetworking.a

验证了静态库是目标文件.O的合集。
整个编译过程先是把源文件编译生成目标文件(.o),再通过链接器生成可执行文件或者动态库。
1、查看clang命令
需要用到clang,那我们先了解一下clang是什么,在终端输入man clang。

clang是C、C++、Objective-C的编译器。它也是一个工具的合集,包含了预处理、解析、优化、代码生成、汇编化、链接。
2、使用clang命令,将.m文件编译成.o文件。
使用了AFNetworking的情况下,需要将AFN的头文件加载到编译命令中,库文件在编译时不需要,在链接时会根据头文件中的重定位符号表,去找到库文件中具体的符号。
clang -x objective-c -target x86_64-apple-macos10.15.7 -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk -I ./AFNetworking -c test.m -o test.o
-
-x:指定编译文件语言类型 -
-target:指定生成架构 -
-fobjc-arc:使用ARC -
-isysroot:使用SDK的路径 -
-I:指定头文件路径Header Search Paths -
-c:生成目标文件 -
-o:输出文件

如果出现isysroot错误
no such sysroot directory: '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.3.sdk' [-Wmissing-sysroot]
可以在访达中通过command+shift+G查找路径/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/找到电脑中的sdk。

- 目标文件中包含重定位符号表,它保存了文件中使用的所有符号,链接时会根据重定位符号表生成具体的符号信息。
- 所以在生成目标文件时,只需静态库的头文件即可。重定位符号表只需要记录哪个地方的符号需要重定位,然后在链接过程中会自动将符号重定位。
3、由.o目标文件生成可执行文件
clang -target x86_64-apple-macos10.15.7 -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk -L./AFNetworking -lAFNetworking test.o -o test
-
-L:指定库文件路径(.a\.dylib库文件)Library Search Paths -
-l:指定链接的库文件名称(.a\.dylib库文件)Other Linker Flags -lAFNetworking
通过库文件路径下的文件名,可以自动找到libAFNetworking.a那个库文件。
静态库链接成功的三要素:
-I:指定头文件路径Header Search Paths-L:指定库文件路径(.a\.dylib库文件)Library Search Paths-l:指定链接的库文件名称(.a\.dylib库文件)Other Linker Flags -lAFNetworking
创建静态库
验证静态库是.o文件的合集。

1、将TestExample.m生成TestExample.o文件
将一个.m文件生成一个.o文件,未使用第三方库的情况
chenshuangchao@chenshuhaodeMBP 链接静态库AFNetworking % cd /Users/chenshuangchao/Desktop/静态库原理/StaticLibrary
chenshuangchao@chenshuhaodeMBP StaticLibrary % clang -x objective-c -target x86_64-apple-macos10.15.7 -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk -c TestExample.m -o TestExample.o
2、将TestExample.o文件变为静态库
因为静态库就是.o文件的合集,那么一个.o文件也可以是一个合集,一个.o文件也可以是一个静态库,可以直接将.o文件的后缀改为.a或者.dylib。
TestExample.o-->libTestExample.dylib-->libTestExample
或者使用ar命令: ar -rc libTestExample.a TestExample.o
注意:一定要命名为
libTestExample,-l指令可以找到libTestExample,但是找不到TestExample
3、将test.m生成test.o
在test.m中调用TestExample类生成的静态库。必须指定头文件。
clang -x objective-c -target x86_64-apple-macos10.15.7 -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk -I ./StaticLibrary -c test.m -o test.o
4、将test.o链接生成可执行文件
clang -target x86_64-apple-macos10.15.7 -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk -L./StaticLibrary -lTestExample test.o -o test
5、验证可执行文件test
在终端输入lldb,进入lldb环境。
运行file test,将可执行文件包装成一个target。
使用r命令,开始运行。
静态库合并
静态库合并的两种方式:
- 使用
ar命令- 使用
Xcode提供的libtool命令
通常会使用libtool。
ar命令
- 可以查看静态库
- 也能将多个.o文件合并成静态库
- 可以将静态库中包含的.o文件全部解压出来
- 使用
ar x libAFNetworking.a命令,解压libAFNetworking.a静态库。 -
ar r lib_AF_SD.a *.o命令,将目录下的所有.o文件,合并成一个.a文件
对两个.a文件的合并,可以通过先将.a文件解压成.o文件,再将所有.o文件合并。
libtool合并.a文件
libtool -static -o libCSC.a libAFNetworking.a libSDWebImage.a

合并成功,然后使用objdump查看重定位符号。
objdump --macho --rebase libCSC.a
Framework
Framework实际上是⼀种打包⽅式,将库的⼆进制⽂件,头⽂件和有关的资源⽂件打包到⼀起,⽅便管理和分发。
Framework和系统的UIKit.Framework还是有很⼤区别。系统的Framework不需要拷⻉到⽬标程序中,而自定义的Framework哪怕是动态的,最后也要拷⻉到App中(App和Extension的Bundle是共享的),因此苹果⼜把这种Framework称为Embedded Framework。
Framework可以是静态库,也可以是动态库。论是静态库还是动态库,Framework都包含了头文件、库的原始文件、签名和资源文件。
mudule
mudule是clang提供的解析.h头文件的一种解析格式
mudule可以把.h头文件预先编译成二进制,存储到系统缓存目录中。好处:当编译.m文件时,避免反复编译.h文件
例如:当一个.h文件在很多.m中被import,当这些.m文件被编译时,不需要一遍又一遍的重复编译.h文件,它会直接使用mudule预先编译好的二进制
Auto-Link
Auto-Link是LC_LINKER_OPTION链接器的特性。启用该特性后,在import <模块>时不需要再往链接器去配置链接参数。
例如:import <Framework>,代码中使用这个Framework格式的库文件,在生成目标文件时,会自动在目标文件的Mach-O中,插入一个load command,格式是LC_LINKER_OPTION,存储链接器参数-framework <Framework>。
创建Framework
准备的文件
第一步:由TestExample.m生成TestExample.o
clang -x objective-c -target x86_64-apple-macos10.15.7 -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk -c TestExample.m -o TestExample.o

第二步:将TestExample.o文件格式去掉.o,变成文本编辑文稿

第三步:新建一个文件夹,改为TestExample.framework
- 在文件夹内新建
Headers文件夹 - 将TestExample.h移动到
Headers中 - 然后将
libTestExample.a文件去掉后缀名,改为TestExample - 像正式项目一样,将
TestExample.framework移动到Framework文件夹下
第三步:模仿静态库创建framework.png
第四步:将test.m编译成test.o
在test.m中调用了TestExample.h
clang -x objective-c -target x86_64-apple-macos10.15.7 -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk -I ./Frameworks/TestExample.framework/Headers -c test.m -o test.o
第五步:将test.o链接framework,连接成可执行文件
clang -target x86_64-apple-macos10.15.7 -fobjc-arc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk -F./Frameworks -framework TestExample test.o -o test
-
-F:指定Framework所在目录Framework Search Paths -
-framework:指定链接Framework的名称Other Linker Flags -framework AFNetworking
第六步:LLDB环境验证。

Framework链接成功的三要素:
-
-I:指定头文件路径Header Search Paths -
-F:指定Framework所在目录Framework Search Paths -
-framework:指定链接Framework的名称Other Linker Flags -framework AFNetworking
Shell脚本初探

可以利用shell脚本,避免文件夹的来回切换。

SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk
FILE_NAME=test
HEADER_SEARCH_PATH=./StaticLibrary
echo "第一步-----开始编译test.m"
clang -x objective-c \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot $SYSROOT \
-I${HEADER_SEARCH_PATH} \
-c ${FILE_NAME}.m -o ${FILE_NAME}.o
echo "第二步-----开始进入StaticLibrary,将TestExample.m编译成TestExample.o"
pushd ./StaticLibrary
clang -x objective-c \
-target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot $SYSROOT \
-c TestExample.m -o TestExample.o
echo "第三步-----将TestExample.o修改为libTestExample.a"
ar -rc libTestExample.a TestExample.o
echo "第四步-----退出StaticLibrary,开始test.o链接成test"
popd
clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot $SYSROOT \
-L./StaticLibrary \
-lTestExample \
${FILE_NAME}.o -o ${FILE_NAME}
dead_strip与静态库
一、探究死代码剥离的现象
链接库文件的过程中,默认设置为-noall_load,符合剥离条件的代码全部剥离。

首先在test.m中引入#import "TestExample.h",但是注释掉对象实例化的代码。利用上面的shell脚本,生成可执行文件。

使用objdump --macho -d test命令,查看__TEXT代码段信息。

把test.m的注释取消掉,也就是在test.m中使用TestExample类,再把上面的流程重新走一遍。

再次执行发现可执行文件中包含了lg_test。
两次对比,验证了clang在链接过程中,默认设置为-noall_load。
二、实际项目中出现问题的场景
依赖库中存在分类时,如果Other Linker Flags使用默认的-noall_load,在主项目中调用依赖库的分类方法,会发生什么?
新建一个主项目,给主项目添加workspace。
打开主项目 -> File -> Save as workspace
再将依赖库添加到workspace进行链接。
打开刚生成的XXX.xcworkspace -> 关掉编辑器中的所有文件 -> 点击左下角加号 -> Add File to "XXX" -> 选择想要添加的库项目。
将静态库添加到主项目中。默认需要先编译静态库,再在主项目中使用静态库,需要在两个target之间来回切换。

能否实现在编译主项目的同时编译静态库呢?
将静态库的framework以
Embed & Sign模式添加到主项目即可。主项目的target -> General -> Frameworks, Libraries, and Embedded Content ->加号添加或者把framework拖进来
-
Do Not Embed:不会将Framework拷贝到IPA包里。用于静态库 - 项目使用的
Framework是静态库,链接时静态库的代码和符号会跟App进行合并,故此不需要将Framework拷贝到IPA包里 -
Embed & Sign:将Framework拷贝到IPA包里,并进行签名操作。用于动态库 -
Embed Without Signing:如果Framework已有正确签名,可以使用此项
在主项目中调用依赖库的分类方法。

结果:直接运行会发生奔溃,奔溃的原因是找不到方法,因为分类方法是运行时查找的,链接器默认是
-noall_load,在库链接时,会把无用的代码剥离掉,运行时就会出现找不到方法,发生奔溃。
解决办法:
在项目中使用-all_load,不剥离任何代码,那肯定可以调用成功。但是这样就无法享受到编译器为我们准备的剥离功能。我们希望的是,按我们的意愿来剥离库,所以-all_load不是最优解。
如何指定库强制加载?
在主项目中创建XCConfig,将XCConfig配置到Tatget上,然后写入以下代码:
LGSTATIC_FRAMEWORK_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/LGStaticFramework.framework/LGStaticFramework
OTHER_LDFLAGS=-Xlinker -force_load ${LGSTATIC_FRAMEWORK_PATH}
这段代码的作用是通过Xlinker让clang告诉链接器,让LGStaticFramework这个库强行加载,不要剥离。

-
-noall_load:默认值,符合剥离条件的代码全部剥离 -
-all_load:不要剥离任何代码 -
-ObjC:OC代码不要剥离 -
-force_load:指定某个静态库不要剥离代码,参数后面需要拼接静态库路径
三、dead_strip死代码剥离
-noall_load和Xcode中Dead Code Stripping配置项,完全不是一个东西。
- 库链接配置决定,剥离的规则,在
ld中有-noall_load、-all_load、-ObjC、-force_load四种参数的配置。只有在链接对象是一个静态库时,才会起效。 - 在ld中指定
-dead_strip参数,是编辑器根据我们的配置,自动的做死代码剥离。
死代码剥离的配置.png
Link-Time Optimization
多个.o文件链接成可执行文件,和.o链接静态库有一些差异:
-
.o链接成可执行文件:先将多个.o文件合并成一个大的.o文件,再去链接成可执行文件。先组合再链接,故此Dead Code Stripping的优化无法生效 -
.o链接静态库:相当于.o直接使用静态库。先Dead Code Stripping再使用
此时可以使用链接器提供的另一个参数,LTO(Link-Time Optimization)链接时间优化
使用Monolothic选项,在多个.o文件链接时进行优化,此优化在Dead Code Stripping之后触发

