美文网首页
9、iOS强化 --- 静态库

9、iOS强化 --- 静态库

作者: Jax_YD | 来源:发表于2021-03-12 11:42 被阅读0次
  • 什么是静态库?
    静态库也叫做静态链接库,可以简单的看做一组目标文件的集合。即多个目标文件经过压缩打包后形成的文件。
    在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的静态库:

image.png
在终端执行下面的命令,来看一下libAFNetworking.a的格式:
file libAFNetworking.a
image.png
可以看到libAFNetworking.a是一个文档的格式。

下面我们再来看一下libAFNetworking.a里面到底有什么。
这里我们要用到ar命令,我们可以通过man ar来看一ar是干什么的:

image.png
我们通过ar -t 这个命令来打印一下libAFNetworking.a里面的内容:
image.png
可以看到libAFNetworking.a就是.o文件的集合。

静态库链接

下面我们将.a文件与我们的代码链接生出可执行程序。
首先我们有一个test.m文件

image.png
下面我们来编译生成test.o文件:
image.png
这是我们发现,编译报错,因为找不到AFNetworking.h文件。我们在test.m文件引用了AFNetworking,但是在编译的过程中我们并没有告诉编译器AFNetworking.h的地址,类似于我们Xcode中header search paths的报错。
⚠️ 指令中的\shell里面的转移字符,可以让指令换行,但是还是一条指令,这样方便阅读。

下面我们来设置一下AFNetworking.h的路径:
我们通过-I来指定目录,.代表当前文件夹

image.png
image.png
这样生成目标文件test.o成功了。
接下来生成可执行文件test
执行下面的命令:
image.png
image.png

静态库的合并

通过上面我们知道,静态库.o问价的合集,那么静态库的合并其实就是将.a先解压,然后再合并成一个.a文件。
这里我们用libtoolar也可以做到,看上ar的解释;通常会用libtool来做这件事)。
那我们来看一下libtool

image.png
  • 现在有两个静态库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文件里面的内容:

image.png
接下来我们手动创建一个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中文件的格式,将后缀去掉等。

image.png

接下来我们编译一下,生成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是一种解释型的语言,会一行一行的去解释你的代码。
首先我们搭建我们的测试环境,创建一个文件夹,文件夹内容如下:

image.png

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}

第一次运行,汇报这个错:

image.png
是因为build.sh文件没有可执行权限,那么我们给它加一个权限就可以了:
chmod +x ./build.sh
// x 代表可执行权限

然后再执行脚本:


image.png

-noall_load

clang链接的过程中-noall_load默认就是生效的。
那么这个时候就产生了一个问题,大家都知道,OC的分类(Category)是在运行时动态创建的。那么在链接的时候,发现分类的方法没有被调用,就会被剥离。
下面看一下具体的场景。
我们有一个静态库 如下:

image.png
和 一个APP工程 如下:
image.png
  • 第一步我们将两个工程引入到同一个workspace中:
    创建workspace

    image.png
    (创建workspace的时候,主要保存到相应的文件。此时是在YSStaticLibrary工程中做到操作。)
    添加其他工程workspace:
    image.png
    选择对应的project
    image.png
    最后通过wookspace从新打开工程即可。
  • 为了方便编译,我可以这样做:


    image.png
  • 我们在YSAPP中使用一下我们的静态库,然后运行:

    image.png
    会发现,马上就报错了:
    image.png
    这就是因为链接的过程中将ys_test_category剥离了(分类是在运行时创建的)。
  • 这里给大家提示一下(不要把我们APP工程里面的分类 和 静态库里面的分类搞混了):
    .o.o 生成可执行文件 ,是先合并成一个大的.o 然后再去链接
    .o.a 是先dead code strip 再去链接
    -noall_load这些只针对静态库有效。(\color{red}{看下面代码注释,这一点很关键}

这个时候我们可以通过添加链接器参数也保留静态库的符号:

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 多环境的配置
再运行就可以了。

image.png

相关文章

网友评论

      本文标题:9、iOS强化 --- 静态库

      本文链接:https://www.haomeiwen.com/subject/uhfoqltx.html