iOS - 静态库探索

552 阅读8分钟

静态库

库的基础知识

什么是静态库

静态库即静态链接库:可以简单的看成⼀组⽬标⽂件的集合。即很多⽬标⽂件经过压缩打包后形成的⽂件。Windows 下的 .lib,Linux 和 Mac 下的 .a。Mac独有的.framework。

缺点:浪费内存和磁盘空间,模块更新困难

什么是动态库

与静态库相反,动态库在编译时并不会被拷⻉到⽬标程序中,⽬标程序 中只会存储指向动态库的引⽤。等到程序运⾏时,动态库才会被真正加 载进来。格式有:.framework、.dylib、.tdb。

缺点: 会导致⼀些性能损失。但是可以优化,⽐如延迟绑定(Lazy Binding)技术

常用的库文件格式
  1. .a:静态库
  2. .dylib :动态库
  3. .framework :静态库或动态库
  4. .xcfrmework :framework的另⼀种先进格式 本篇主要介绍.a.framework 格式的静态库

自定义 .a 静态库

项目目录

qhb_03_01_静态库原理目录.jpg

CustomLib.h中声明方法 -(void)yj_print:(NSString *)str qhb_03_02_CustomLib-h.jpg

CustomLib.m中实现方法 -(void)yj_print:(NSString *)str qhb_03_03_CustomLib-m.jpg

test.m中导入头文件 #import "CustomLib.h",创建lib并调用 yj_print qhb_03_04_test-m.jpg

生成.o文件

上面我们说了:静态库可以简单的看成⼀组⽬标⽂件的集合。即很多⽬ 标⽂件经过压缩打包后形成的⽂件,那我们将先从生成.o文件开始

test.m -> test.o 打开终端,输入如下命令: qhb_03_05_generate-o_01.jpg

点击 Enter 运行,发现报错CustomLib.h file not found(有没有很熟悉,我们平常项目中引入三方库的时候是不是经常会遇到这个错误)。这是因为test.m中引用了CustomLib.h但我们并没有告诉编译器到哪里去找CustomLib.h qhb_03_06_generate-o_02.jpg

这时候需用到-I参数,来指定头文件目录,这样就ok了。 qhb_03_07_generate-o_03.jpg

完整命令:

clang -x objective-c \
-target arm64-apple-macos11.0 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \
-I./StaticLibrary \
-c test.m -o test.o 

切换到StaticLibrary目录,使用同样的方法将生成 CustomLib.m -> CustomLib.o qhb_03_08_generate-o_04.jpg CusmtomLib.m中没有引用其它头文件所以这里不用-I参数

命令参数说明

  • clang : C, c++和Objective-C编译器(可以在终端输入 man clang 来查看具体说明)
  • -x : 指定编译文件语言类型
  • -target : 指定目标架构、平台(arm/x86、iphone/mac)
  • -fobjc-arc : 指定为arc环境
  • -isysroot : 使用的系统SDK路径
  • -c : 生成目标文件,只运行preprocess,compile,assemble,不链接`
  • -o : 输出文件
  • -I<directory> : 在指定目录寻找头文件, 对应Xcode的:header search path
  • 每行后面的\是转义字符,告诉终端这里回车是换行,不是执行
生成.a文件

两种生成.a静态库方法:

  1. 使用ar命令生成 qhb_03_09_generate-a_01.jpg
  • ar: 压缩目标文件,并对齐进行编号和索引,形成静态库。同时也可以解压缩静态库,查看有哪些静态库
  • -r: 将指定的.o文件替换或添加到.a文件。如果.a文件不存在,则创建一个新的.a文件
  • -c : 不输出任何信息
  • -t : 列出.a文件包含的目标文件

qhb_03_10_ar-t_show 更多参数说明可在终端通过 man ar 查询

  1. 使用libtool命令生成 qhb_03_11_libtool-a.jpg 完整命令:
libtool -static -arch_only arm64 \
-D -syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \
CustomLib.o -o libCustomLib2.a
  • libtool : 可以创建静态库和动态库
  • -static : 创建静态库
  • -arch_only : 指定架构
  • -syslibroot : 使用的系统SDK
  • 更多参数说明可在终端通过 man libtool 查询
链接.a生成可执行文件

test.o链接libCustomLib.a生成可执行文件test qhb_03_12_link-a.jpg 上面分别链接 libCustomLib.a、 libCustomLib2.a 生成 test、test2,且分别运行test,test2都如预期调用了我们静态库中CustomLib.myj_print的实现

完整命令:

clang -target arm64-apple-macos11.0 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \
-L./StaticLibrary \ 
-lCustomLib \         
test.o -o test
  • -L<dir>: 指定库文件路径(.a/.dylib文件),对应Xcode中的 Library Search Path
  • -l<library_name> : 指定链接的库文件名(.a/.dylib文件),对应Xcode中的 other link flags,如:-lAFNetworking

注意: 上面我们生成的静态库都是lib开头的,但是链接时-l参数后面的库名称明没有写lib。这是因为库文件查找有如下规则: 先找lib+<library_name>的动态库,找不到,再去找lib+<library_name>的静态库,还找不到,就报错 所以这里我们不用加 lib

总结

编译、链接 三要素:

  • -I<dir>: 在指定目录寻找头文件, 对应Xcode中的:header search path
  • -L<dir>: 指定库文件路径(.a/.dylib文件),对应Xcode中的 Library Search Path
  • -l<library_name> : 指定链接的库文件名(.a/.dylib文件),对应Xcode中的 other link flags,如:-lAFNetworking

自定义 .framework 静态库

.framework 目录机构

我们先来看一下平常用到的framework目录结构: qhb_03_13_framwork_01.jpg 可以看到大致结构是在 library_name.framework目录下保存库文件(静态库或动态库)+headers(保存头文件)

按照这个结构来创建自己的framework(在上面.a静态库的基础上进行),目录如下: qhb_03_14_framework_02.jpg

生成.o .a文件

操作步和自定义.a静态库一样,在这儿就不在重复了,直接展示结果: qhb_03_16_framework_04.jpg

库文件CustomLib生成命令: qhb_03_15_framwork_03.jpg

这里库文件并没有以lib开头.a结尾。

其实刚开始的时候我是使用的 ar -rc libCustomLib.a CustomLib.o 生成的.a静态库,但是后面 链接 .framework 的时候报错ld: framework not found CustomLib。后来我使用 libtool命令生成libCustomLib.a,结果链接时还是报错。

最终我用 libtool命令生成没有加lib前缀,和.a后缀,然后这样链接就通过了(具体我也不知道为啥,但是看三方的framework里面库文件名好像都没有后缀、前缀)

链接.framework生成可执行文件

qhb_03_17_framwork_05.png

完整命令:

clang -target arm64-apple-macos11.0 \
-fobjc-arc \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \
-F./Frameworks \      
-framework CustomLib \
test.o -o test

这里和链接.a静态库的区别就 -F、-framework 参数

  • -F<directory> : 在指定的目录寻找.framework路径,对应Xcode中的framework search path
  • -framework <framework_name> : 指定链接的 .framework 库名,对应Xcode中的 other linker flags-framework

如此这般.framework静态库就ok了

合并静态库

生成两个静态库

这里直接copy了一份的源文件CustomLib.h/.m,改了下名,然后重新生成了CustomLib2静态库

qhb_03_18_merge_staticlib_01.png

test.m中新引入了CustomLib2.h,并实例化了lib2对象,调用yj_print方法 qhb_03_19_merge_staticlib_02.png

合并前生成test可执行文件

qhb_03_20_merge_staticlib_03.png

合并

qhb_03_21_staticlib_04.png

这个命令也很简单,就是libtool -static -o '合并后的库文件名' lib1_path lib2_path

链接合并后的静态库

创建MergeLib.framework,将MergeLib添加到MergeLib.framework目录:

qhb_03_22_staticlib_05.png

链接合并后的静态库生成test2可执行文件

qhb_03_22_staticlib_06.png

shell初探

上面在终端执行的一系列命令,每次都要手写或复制粘贴,甚是麻烦,有没有一种简单高效的方法,来生成我们想要的文件。有———shell script

这里以:自定义.framework静态库为例,通过 shell script来完成:.o .a文件生成,链接.framework 最终运行可执行文件 test

文件目录:

qhb_03_24_shell_01.png

build.sh 编写
# 自定义变量:
# 系统库路径
SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
# 可执行文件名
FILE_NAME=test
# 编译架构
ARCH=arm64-apple-macos11.0

echo "-- 1 -- OC 源文件 test.m 生成目标文件 test.o"
clang -x objective-c \
-target ${ARCH} \
-fobjc-arc \
-isysroot ${SYSROOT} \
-I./Frameworks/CustomLib.framework/Headers \
-c test.m -o test.o

echo "-- 2 -- 跳转到 Frameworks/CustomLib.framework"
pushd ./Frameworks/CustomLib.framework

echo "-- 3 -- 生成静态库文件 CustomLib"
echo "-- 3.1 -- OC 源文件 CustomLib.m 生成目标文件 CustomLib.o"
clang -x objective-c \
-target ${ARCH} \
-fobjc-arc \
-isysroot ${SYSROOT} \
-I./Headers \
-c CustomLib.m -o CustomLib.o

echo "-- 3.2 -- 生成目标文件 CustomLib.o 生成静态库文件 CustomLib"
libtool -static -arch_only arm64 -D -syslibroot ${SYSROOT} CustomLib.o -o CustomLib

echo "-- 4.0 -- 返回 ”05-shell简单应用“ 目录"
popd

echo "-- 5.0 -- test.o 链接静态库 CustomLib.framework 生成可执行文件 test"
clang -target ${ARCH} \
-fobjc-arc \
-isysroot ${SYSROOT} \
-F./Frameworks \
-framework CustomLib \
test.o -o test

echo "-- 6.0 -- 生成可执行文件 test 成功"

echo "-- 7.0 -- 运行可执行文件 test"
./test

执行build.sh

qhb_03_25_shell_02.png

补充

初识module

moduleclang提供的一种专门用来处理头文件的解析格式。 同一个头文件可能在多个地方被引用,每个文件编译的时候这个头文件都会编译,这就导致同一个头文件被编译多次,浪费时间。module会预先将头文件编译成二进制文件,缓存到指定目录。 这样在其它文件中引入头文件时不在需要一次次的编译

Auto-link

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

.framework

.framework 实际上是⼀种打包⽅式,将库的⼆进制⽂件,头⽂件和有关的资源⽂件打包到⼀起,⽅便管理和分发。 自定义的 .framework 和系统的,如UIKit.framework 还是有很⼤区别。系统的 .framework 不需要拷⻉到⽬标程序中,我们⾃⼰做出来的 .framework 哪怕 是动态的,最后也还是要拷⻉到 App 中(App 和 Extension 的 Bundle 是 共享的),因此苹果⼜把这种 自定义.framework 称为 Embedded Framework

连接器参数

  • -no_all_load: Xcode 默认配置,不加载全部
  • -all_load: 加载静态库全部代码
  • -ObjC: 加载静态库中全部OC相关代码,包括分类
  • -force_load : 强制加载指定静态 注意:以上这四个参数都只是针对静态库的