静态库原理
.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
格式的库文件,在生成目标文件时,会自动在目标文件的Mach-O
中,插入一个load command
,格式是LC_LINKER_OPTION
,存储链接器参数-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
文件夹下
第四步:将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
参数,是编辑器根据我们的配置,自动的做死代码剥离。
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
之后触发