- 什么是静态库?
静态库也叫做静态链接库,可以简单的看做一组目标文件的集合。即多个目标文件经过压缩打包后形成的文件。
在iOS开发中,常见的静态库有:
.a
文件(是一个文档格式)
.framework
文件(该文件可以是静态库,也可以是动态库)
静态库的缺点:浪费内存和磁盘空间,模块更新困难。
这里我们解释一下接下来我们要用到的clang
命令:
/**
clang命令参数:
-x: 指定编译文件语言类型
-g: 生成调试信息
-c: 生成目标文件,只运行preprocess,compile,assemble,不链接
-o: 输出文件
-isysroot: 使用的SDK路径
1. -I<directory> 在指定目录寻找头文件 header search path
2. -L<dir> 指定库文件路径(.a\.dylib库文件) library search path
3. -l<library_name> 指定链接的库文件名称(.a\.dylib库文件)other link flags -lAFNetworking
-F<directory> 在指定目录寻找framework framework search path
-framework <framework_name> 指定链接的framework名称 other link flags -framework AFNetworking
-l的查找规则:先找lib+<library_name>的动态库,找不到,再去找lib+<library_name>的静态库,还找不到,就报错
*/
.a
文件
首先我们先来看一下.a
的格式是什么?
我这边有一AFNetworking
的静态库:
在终端执行下面的命令,来看一下
libAFNetworking.a
的格式:
file libAFNetworking.a
image.png
可以看到
libAFNetworking.a
是一个文档的格式。
下面我们再来看一下libAFNetworking.a
里面到底有什么。
这里我们要用到ar
命令,我们可以通过man ar
来看一ar
是干什么的:
我们通过
ar -t
这个命令来打印一下libAFNetworking.a
里面的内容:image.png
可以看到
libAFNetworking.a
就是.o
文件的集合。
静态库链接
下面我们将.a
文件与我们的代码链接生出可执行程序。
首先我们有一个test.m
文件
下面我们来编译生成
test.o
文件:image.png
这是我们发现,编译报错,因为找不到
AFNetworking.h
文件。我们在test.m
文件引用了AFNetworking
,但是在编译的过程中我们并没有告诉编译器AFNetworking.h
的地址,类似于我们Xcode中header search paths
的报错。⚠️ 指令中的
\
是shell
里面的转移字符,可以让指令换行,但是还是一条指令,这样方便阅读。
下面我们来设置一下AFNetworking.h
的路径:
我们通过-I
来指定目录,.
代表当前文件夹
image.png
这样生成目标文件
test.o
成功了。接下来生成可执行文件
test
执行下面的命令:
image.png
image.png
静态库的合并
通过上面我们知道,静态库
是.o
问价的合集,那么静态库的合并
其实就是将.a
先解压,然后再合并成一个.a
文件。
这里我们用libtool
(ar
也可以做到,看上ar
的解释;通常会用libtool
来做这件事)。
那我们来看一下libtool
:
- 现在有两个静态库
libAFNetworking.a
&libSDWebImage.a
,执行下面的指令进行合并:
libtool -static -o test.a libAFNetworking.a libSDWebImage.a
image.png
Framework
image.png- Mac OS / iOS 平台还可以使用
Framework
。
Framework
实际上是一种打包方式,将库的二进制文件,头文件和有关的资源文件打包到一起,方便管理和分发。
在使用到Framework
里面的文件的时候,那么生成.o
文件和上面试一样,因为-I
只是一个路径,头文件和库文件本身并不一定要放到一起。
我们来看一下Framework
文件里面的内容:
接下来我们手动创建一个
Framework
,暂时我们用不到签名和资源文件,所以我们只需要头文件
和.a
就可以了。我们按照正常的工程目录来创建:
image.png
这里我们有这样一组文件:
image.png
我们先生成
.a
文件:image.png
这里
YSExample.o
生成 YSExample.a
的时候用到了下面的命令:
ar -rc TestExample.a TestExample.o
/**
`ar`压缩目标文件,并对其进行编号和索引,形成静态库。同时也可以解压缩静态库,查看有哪些目标文件:
ar -rc a.a a.o
-r: 像a.a添加or替换文件
-c: 不输出任何信息
-t: 列出包含的目标文件
*/
接着我们将YSExample.a
放到刚刚创建的路径里面,同时按照我们看到的Framework
中文件的格式,将后缀去掉等。
接下来我们编译一下,生成test.o
文件:
clang -x objective-c \
-target x86_64-apple-macos11.1 \
-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
image.png
然后执行:
clang -target x86_64-apple-macos11.1 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.1.sdk \
-F./Frameworks \
-framework TestExample \
test.o -o test
image.png
使用shell脚本
来执行上面的操作
shell
是一种解释型的语言,会一行一行的去解释你的代码。
首先我们搭建我们的测试环境,创建一个文件夹,文件夹内容如下:
build.sh
:将test.m
赋值,名字和后缀改一下就可以了。
接着我们将我们要在终端执行的命令,拷贝到build.sh
里面(这些命令就不在做解释,上面都有解释):
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"
pushd ./StaticLibrary
clang -x objective-c \
-target x86_64-apple-macos11.1 \
-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-macos11.1 \
-fobjc-arc \
-isysroot $SYSROOT \
-L./StaticLibrary \
-lTestExample \
${FILE_NAME}.o -o ${FILE_NAME}
第一次运行,汇报这个错:
是因为
build.sh
文件没有可执行权限,那么我们给它加一个权限就可以了:
chmod +x ./build.sh
// x 代表可执行权限
然后再执行脚本:
image.png
-noall_load
clang
链接的过程中-noall_load
默认就是生效的。
那么这个时候就产生了一个问题,大家都知道,OC的分类(Category
)是在运行时动态创建的。那么在链接的时候,发现分类的方法没有被调用,就会被剥离。
下面看一下具体的场景。
我们有一个静态库 如下:
和 一个APP工程 如下:
image.png
-
第一步我们将两个工程引入到同一个
image.pngworkspace
中:
创建workspace
(创建workspace
的时候,主要保存到相应的文件。此时是在YSStaticLibrary
工程中做到操作。)
添加其他工程
到workspace
:
image.png
选择对应的project
image.png
最后通过wookspace
从新打开工程即可。 -
为了方便编译,我可以这样做:
image.png -
我们在
image.pngYSAPP
中使用一下我们的静态库,然后运行:
会发现,马上就报错了:
image.png
这就是因为链接的过程中将ys_test_category
剥离了(分类是在运行时创建的)。 -
这里给大家提示一下(不要把我们APP工程里面的分类 和 静态库里面的分类搞混了):
①.o
和.o
生成可执行文件 ,是先合并成一个大的.o 然后再去链接
②.o
和.a
是先dead code strip 再去链接
③-noall_load
这些只针对静态库有效。()
这个时候我们可以通过添加链接器参数也保留静态库的符号:
LGSTATIC_FRAMEWORK_PATH=${BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/YSStaticLibrary.framework/YSStaticLibrary
OTHER_LDFLAGS=-Xlinker -force_load $LGSTATIC_FRAMEWORK_PATH
// OTHER_LDFLAGS 通过clang 给ld传递参数
// -Xlinker 告诉clange 参数是传给 ld的不是传给你的
// -noall_load 不全部加载
// -all_load 所有的都加载
// -Objc 除了OC的代码,其他的正常剥离
// -force_load 指定哪些静态库不要 dead code strip
/// 以上四个参数只是针对我们的静态库
image.png
将上面的指令,添加到对应的xcconfig
文件中就可以了。对于配置xcconfig
文件不太明白的同学可以参考2、iOS强化 --- Xcode 多环境的配置。
再运行就可以了。
网友评论