静态库
库的基础知识
什么是静态库
静态库即静态链接库:可以简单的看成⼀组⽬标⽂件的集合。即很多⽬标⽂件经过压缩打包后形成的⽂件。Windows 下的 .lib,Linux 和 Mac 下的 .a。Mac独有的.framework。
缺点:浪费内存和磁盘空间,模块更新困难
什么是动态库
与静态库相反,动态库在编译时并不会被拷⻉到⽬标程序中,⽬标程序 中只会存储指向动态库的引⽤。等到程序运⾏时,动态库才会被真正加 载进来。格式有:.framework、.dylib、.tdb。
缺点: 会导致⼀些性能损失。但是可以优化,⽐如延迟绑定(Lazy Binding)技术
常用的库文件格式
.a:静态库.dylib :动态库.framework :静态库或动态库.xcfrmework :framework的另⼀种先进格式本篇主要介绍.a和.framework格式的静态库
自定义 .a 静态库
项目目录
CustomLib.h中声明方法 -(void)yj_print:(NSString *)str
CustomLib.m中实现方法 -(void)yj_print:(NSString *)str
test.m中导入头文件 #import "CustomLib.h",创建lib并调用 yj_print
生成.o文件
上面我们说了:静态库可以简单的看成⼀组⽬标⽂件的集合。即很多⽬ 标⽂件经过压缩打包后形成的⽂件,那我们将先从生成.o文件开始
test.m -> test.o
打开终端,输入如下命令:
点击 Enter 运行,发现报错CustomLib.h file not found(有没有很熟悉,我们平常项目中引入三方库的时候是不是经常会遇到这个错误)。这是因为test.m中引用了CustomLib.h但我们并没有告诉编译器到哪里去找CustomLib.h
这时候需用到-I参数,来指定头文件目录,这样就ok了。
完整命令:
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
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静态库方法:
- 使用
ar命令生成
ar: 压缩目标文件,并对齐进行编号和索引,形成静态库。同时也可以解压缩静态库,查看有哪些静态库-r: 将指定的.o文件替换或添加到.a文件。如果.a文件不存在,则创建一个新的.a文件-c: 不输出任何信息-t: 列出.a文件包含的目标文件
更多参数说明可在终端通过
man ar 查询
- 使用
libtool命令生成完整命令:
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
上面分别链接
libCustomLib.a、 libCustomLib2.a 生成 test、test2,且分别运行test,test2都如预期调用了我们静态库中CustomLib.m中yj_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目录结构:
可以看到大致结构是在
library_name.framework目录下保存库文件(静态库或动态库)+headers(保存头文件)
按照这个结构来创建自己的framework(在上面.a静态库的基础上进行),目录如下:
生成.o .a文件
操作步和自定义.a静态库一样,在这儿就不在重复了,直接展示结果:
库文件CustomLib生成命令:
这里库文件并没有以lib开头.a结尾。
其实刚开始的时候我是使用的 ar -rc libCustomLib.a CustomLib.o 生成的.a静态库,但是后面 链接 .framework 的时候报错ld: framework not found CustomLib。后来我使用 libtool命令生成libCustomLib.a,结果链接时还是报错。
最终我用 libtool命令生成没有加lib前缀,和.a后缀,然后这样链接就通过了(具体我也不知道为啥,但是看三方的framework里面库文件名好像都没有后缀、前缀)
链接.framework生成可执行文件
完整命令:
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静态库
test.m中新引入了CustomLib2.h,并实例化了lib2对象,调用yj_print方法
合并前生成test可执行文件
合并
这个命令也很简单,就是libtool -static -o '合并后的库文件名' lib1_path lib2_path
链接合并后的静态库
创建MergeLib.framework,将MergeLib添加到MergeLib.framework目录:
链接合并后的静态库生成test2可执行文件
shell初探
上面在终端执行的一系列命令,每次都要手写或复制粘贴,甚是麻烦,有没有一种简单高效的方法,来生成我们想要的文件。有———shell script
这里以:自定义.framework静态库为例,通过 shell script来完成:.o .a文件生成,链接.framework 最终运行可执行文件 test
文件目录:
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
补充
初识module
module是clang提供的一种专门用来处理头文件的解析格式。 同一个头文件可能在多个地方被引用,每个文件编译的时候这个头文件都会编译,这就导致同一个头文件被编译多次,浪费时间。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: 强制加载指定静态 注意:以上这四个参数都只是针对静态库的