ios静态库

1,346 阅读6分钟

常见的库文件格式

  • .a 静态库
  • .dylib 动态库
  • .frmawork 静态库/动态库
  • .xcframework 2018年apple推出的,不同架构下的framework的封装

.a静态库

先通过file命令来查看.a 文件是什么格式

file {fileName}

image.png 其实.a就是一个文档格式. 众所周知.a文件其实就是.o文件的一个合集. 那么怎么验证这一点呢?介绍另一个命令ar

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

通过ar -t命令验证如下 image.png .a文件是.o文件的合集

编译

clang 命令

  • clang 命令: clang is a C, C++, and Objective-C compiler which encompasses preprocessing, parsing, optimization, code genera-tion, assembly, and linking. 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

编译链接

接下来我们链接AFNetworking这个静态库。在本地test.m中有如下代码 image.png image.png 通过如下命令

clang -x objective-c \ 
-target x86_64-apple-macos10.15 \ /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-I./AFNetworking \
-c test.m -o test.o 

执行上述命令之后,生成了一个test.o(目标文件)文件

clang -target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-L./AFNetworking \
-lAFNetworking \
test.o -o test

可能上述命令有点疑惑,为什么链接库的时候不写成libAFNetworking

    test.o链接libAFNetworking.a生成test可执行文件
    -L./AFNetworking 在当前目录的子目录AFNetworking查找需要的库文件
    -lAFNetworking 链接的名称为libAFNetworking/AFNetworking的动态库或者静态库
    查找规则:先找lib+<library_name>的动态库,找不到,再去找lib+<library_name>的静态库,还找不到,就报错

链接库成功的三要素:

  • 指定头文件路径 (header search path)//头文件路径不一定跟库的路径在一起
  • 指定库的路径 (Libraty search path)
  • 指定库的名字

编译静态库

在本地目录下有这样两个文件,里面定义了一个类,类的定义很简单,里面一个TestExample 类,类里面随便定义了一个函数

image.png 现在将这个文件编译成一个.a静态库,通过如下命令生成目标文件

clang -x objective-c \
-target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-c TestExample.m -o TestExample.o

为了证明.a文件就是.o文件的合集,我们将生成的TestExample.o更改为libTestExample.dylib image.png test.m中代码如下 image.png 我们将test.m 文件编译成可执行文件

clang -x objective-c \  \
-target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-I./StaticLibrary \
-c test.m -o test.o

再link称为一个可执行文件

clang -target x86_64-apple-macos10.15 \
> -fobjc-arc \
> -isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
> -L./StaticLibrary \
> -lTestExample \
> test.o -o test

双击执行test, 发现运行一切正常,那么这就说明了.a文件就是.o文件的合集。

静态库合并

介绍一个命令libtool

libtool: create libraries 

在本地目录准备两个静态库 image.png 运行如下命令

 libtool \
> -static \
> -o \
> libML.a \
> /Users/meilinli/selfcode/TestLib/staticlibraryMerge/libAFNetworking.a  \
> /Users/meilinli/selfcode/TestLib/staticlibraryMerge/libSDWebImage.a

就得到了一个libML.a的静态库

libtool 命令参数:
-static //合并的是静态库
-o //合并后的名称

framework

  • framework: Framework实际上是一种打包方式,将库的二进制文件,头文件和有关的资源文件打包到一起,方便管理和分发。 Framework 和系统级别的framework(Fundation.framework之类的)是有很大的区别的。系统的framework不需要拷贝到目标程序中,我们自己创建的framework哪怕是动态的,最后也需要拷贝到app中,因此apple又将这种Framework称为embedded framework. image.png 从之前生成可执行文件的过程来看,其实头文件搜索路径不一定需要跟library搜索路径一样,framework也是利用了这一点。

先通过clang生成了目标文件TestExample.o image.png 运行命令ar -rc libTestExample.a TextExample.o 得到一个libTestExample.a静态库, 然后创建一个TestExample.framwork的文件夹,在此目录下创建一个Headers的文件夹,将TestExample.hcopy到Headers 目录下,并且将libTestExample.a 去掉后缀,删除前面lib放到TestExample.framwork下面,我们就手工生成了一个静态的framework. 结果如下图所示 image.png 那么重新编译test.m 使用如下命令

clang -x objective-c -fmodules \
-target x86_64-apple-maos10.15 \
-isysroot /Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-I./Frameworks/TestExample.framwork/Headers \
-c test.m -o test.o

再链接生成test 可执行文件

clang -target x86_64-apple-macos10.15 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk \
-F./Frameworks \
-framework TestExample \
test.o -o test

shell

之前敲这些命令比较费事,可以创建一个shell脚本

SYSROOT=/Applications/Xcode11.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.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 #进入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 #退出StaticLibrary目录

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}

dead code strip

当我们只是import头文件,但是不使用的时候,编译之后,会有那个头文件的符号吗? image.png 编译生成可执行文件,通过objdump查看下mach-o文件中的text段 image.png 可以看到text段并没有TestExample.h 中的符号. 把之前注释的代码放开,又是什么情况呢? image.png 可以看到,text段边长了。并且用到的符号也在text段。可以得到一个结论clang在链接的过程中Dead code strip默认是生效的。但是这个时候可能会有一些问题,对于分类来说,分类是在runtime的时候创建的。举个例子说明,创建个一个静态库, 详见github链接 image.png image.png 静态库中有一个分类MLOneObject+ML, 中有一个方法test_category, 在MLOneObject中的test方法调用test_category. 创建一个可执行文件工程,link MLTestFramework.framework. image.png 运行起来之后,出现crash image.png 那么怎么解决这个问题呢?可以通过配置链接器,如下图所示 image.png 上图中,指定了链接MLTestFramework.framework的时候是force_load,不会strip dead code.

当链接静态库的时候,strip dead code 不生效的设置方法

  • OTHER_LDFLAG = -XLinker -force_load ${STATIC_LIBRARY_PATH} //link 指定静态库的时候strip dead code不生效
  • OTHER_LDFLAG = -XLinker -ObjC //所有的oc的代码strip dead code不生效
  • OTHER_LDFLAG = -XLinker -all_load //link所有的Mach-O的时候,strip dead code不生效

上述option都是针对的静态库的链接。当然还可以通过连接器参数查看为什么一个符号没有被strip。

-Xlinker -why_live -Xlinker _global_function //_global_function 是去查看的符号

在一个target为可执行文件的工程中,里面有一些并没有用到的类,并且在工程中设置了dead code strip 为 YES, 但是最终并没有把这些没有用到的类的符号strip掉。 image.png 可以设置为Monolithic将这些没有用到的符号strip掉。