补充
上篇文章我们对符号有了一定的认识,这里再补充点关于符号的内容。我们有时候需要知道符号的种类,我们通过命令nm -pa 文件名 来查看符号
我们看到
地址后面有t、d、T等关键字,这些关键字就是符号种类,下面整理了一下符号种类划分
命令查找
上面用到命令-pa,这里我们来说下-pa的含义,-pa其实包含两部分,-p、-a,我们可以通过查看命令符号查看
- 1.在终端输入
man nm,来到name list - 2.向下滚动,查找
-p,-a
我们可以知道
-a:显示所有符号表项,包括插入的使用调试器。-p:不排序;按符号表顺序显示。
静态库
库相关知识
我们常用的库文件格式有.a、.dylib、.framework、.xcframework
.a:常见的静态库.dylib:传统上说的动态库.framework:既有动态库,又有静态库.xcframework:是苹果2018年推出来的,可以将不同架构的库整合到一起。好处就是模拟器,真机可以通用,上架AppStore,不需要将xcframework中的真机架构分离,.framework还需要用脚本分离我们这里主要介绍.a和.framework格式的静态库
库含义
库(Library)说白了就是一段编译好的二进制代码,加上头文件就可以供别人使用。
库的使用时机
- 1.某些
代码需要给别人使用,但是我们不希望别人看到源码,就需要以库的形式进行封装,只`暴露出头文件。 - 2.对于某些
不会进行大的改动的代码,我们想减少编译的时间,就可以把它打包成库,因为库是已经编译好的二进制了,编译的时候只需要Link一下,不会浪费编译时间。
什么是Link
库在使用的时候需要链接(Link),链接的方式有两种:
- 1.
静态 - 2.
动态
什么是静态库
静态库即静态链接库:可以简单的看成一组目标文件的集合。即很多目标文件经过压缩打包后形成的文件。Windows下的 .lib,Linux 和 Mac 下的 .a。Mac独有的.framework。
- 缺点:
浪费内存和磁盘空间,模块更新困难
什么是动态库
与静态库相反,动态库在编译时并不会被拷⻉到目标程序中,目标程序中只会存储指向动态库的引用。等到程序运行时,动态库才会被真正加载进来。格有:.framework、.dylib、.tdb。
- 缺点:
会导致一些
性能损失。但是可以优化,比如延迟绑定(Lazy Binding)技术
如何生成静态库
我们实操一个.m文件,.m如下代码:
上面代码我们导入了
AFN,main函数中使用了AFN的代码,并打印了manager,这就是就是一个很常用的简单的类中引用其它第三方的代码
和这部分代码放在一个文件夹里还有AFN的代码,里面包含了AFN的静态库
我们查看下
libAFNetworking.a到底是什么格式
我们发现是一个
文档格式,文档格式其实就是.o文件的合集,怎么验证呢?
我们需要使用ar
可以看到它
是.o文件的合集
生成.o文件
我们知道编译过程是先生成目标文件(也就是.o文件),之后通过链接器生成可执行文件
所以将我们最上面的.m文件先生称.o文件,然后就会用到我们经常使用的clang,我们来看下clang是什么
就是
C、C++和OC的编译器
下面我们通过clang将.m文件编译成.o文件
- 1.
-x:制定语言,/:转译字符,输入后告诉终端敲回车是换行,不是执行 - 2.
制定平台 - 3.
编译成arc环境 - 4.
指定使用的SDK路径 - 5.
将.m编译成.o但是我们发现报错了,因为我们还引入了AFN,提示我们找不到,所以我们要加入AFN - 1.
I:在指定目录寻找头文件AFN
回车后就生成了
.o文件(上面AFN文件夹和test.m平级,才能这么告诉AFN位置。
生成静态库
下面我们将生成的.o文件通过链接器生成静态库
- 1.这里不需要制定语言,
直接选择架构 - 2.选择
arc环境 - 3.由于项目中
有NSLog,其导出符号,要知道其来源自Foundation,让他去我们指定的Xcode中寻找 - 4.指定目录寻找头文件(
链接的过程就是把我们重定位符号表中的符号进行重定位,这里就需要知道符号的真实位置,AFN的符号表放在了libAFNetworking.a文件里了,它需要和我们生成的test.o文件的符号表进行融合,最后生成一张新的符号表) - 5.指定
连接的库文件名称 - 6.将
.o文件输出为test文件
上面命令敲完后报错,原因是
架构问题,AFN采用的架构是模拟器架构,而我们导出的架构是x86架构,不兼容报错
总结
生成静态库的三要素:
- 1.
-I:在指定目录寻找头文件(在生成.o文件时)对应的是header search path - 2.
-L:指定库文件路径(.a.dylib库文件)对应的是library search path - 3.
-l:指定链接的库文件名称(.a.dylib库文件)对应的是other link flags -lAFNetworking
理解静态库为.o文件合集
创建一个类,在.m写一个OC方法,里面打印
下面我们将
TestExample生成静态库(命令和上面一样)
- 1.先生成.o文件
- 2.下面将生成的.o文件
改成lib.dylib
变成了可执行文件
- 3.将
.dylib删除掉
依然是可执行文件
- 4.查看下文件属性
- 5.我们让
test.o去链接这个libTestExample,如果链接成功了,也就证明了静态库就是.o文件的合集
看到上面生成的文件是没有报错的
- 6.进入
lldb,调用file test对test进行包装,然后在运行r进行运行。
我们看到运行成功了,这也就是我在test.m的输出内容
【总结】:链接成功了,也就说明静态库就是.o文件的合集,验证我们开始说的内容
我们可以通过objdump来查看文件的属性
我们看到它还是个目标文件
【思考】:上面说了静态库就是.o文件的合集,那么合并静态库就是将更多的.o文件合并到一起
合并静态库
合并静态库,用到命令libtool,我们先看下libtool的作用
就是
创建libraries,ranlib:添加或者更新一系列静态库文件格式
下面通过libtool进行静态库合并
- 1.
-static:合并的为静态库 - 2.
-o:输出 - 3.
libLj.a:名字 - 4.
合并的静态库位置路径在
静态库合并中libtool会自动帮我们处理.o文件,通过上面讲解我们知道静态库合并最重要的点就是头文件的处理。这里介绍一个命令:mudule:mudule是用来专门处理.h头文件的命令,它可以将我们的头文件预先编译成二进制然后缓存到目录中,当我们在其它类引入该头文件时,不用再重新编译,直接拿来用就好了(将Swift静态库会再详细讲)之所以提这个,是为了引出编译器的一个特性:Auto-Link
Auto-Link
启用这个特性后,当我们import <模块>,不需要我们再去往链接器去配置链接参数。比如import <framework>我们在代码里使用这个是framework格式的库文件,那么在生成目标文件时,会自动在目标文件的Mach-O中,插入一个 load command格式是LC_LINKER_OPTION,存储这样一个链接器参数-framework <framework>。
Framework
- 定义:
Mac OS/iOS平台还可以使用Framework。Framework 实际上是一种打包方式,将库的二进制文件,头文件和有关的资源文件打包到一起,方便管理和分发。 - 区别:
Framework和系统的UIKit.Framework还是有很大区别。系统的Framework不需要拷⻉到目标程序中,我们自己做出来的Framework哪怕是动态的,最后也还是要拷⻉到App中(App和Extension的Bundle是共享的),因此苹果又把这种Framework称为EmbeddedFramework。
链接Framework
我们链接一个Framework,先准备素材
.a文件的制作在上面已经讲过了
- 1.创建
新的文件夹TestExample.framework
将上面的.h放到Headers文件下,将libTestExample.a
删除.a,删除lib生成红框里的文件,这个就是一个模拟的Framework
- 2.将
test.m编译成.o文件 - 3.
将.o和TestExample.framework进行链接
- -F(directory)在
指定目录寻找framework,相当于framework search path- -framework<framework_name>
指定链接的framework名称,相当于other link flags -framework AFNetworking
制作Framework
上面我们生成可执行文件在终端写了不少命令,每次生成可执行文件都需要写一次命令,有没有一种办法,只需要一行命令就能够生成我们需要的文件呢?答案:有,shell脚本
制作shell脚本
- 1.创建一个shell脚本
- 2.写shell命令
shell语言是一门解释型语言,它会一行一行解释你的语言,而不是像OC,编译好了再去执行
写sell脚本前,先看下目录结构,方便命令理解
写脚本就是将上面终端的命令粘贴复制过来,下面已经写了注释,不再多说明
- 3.执行shell脚本
发现报错:你
没有可执行权限,所以我们要给build.sh加一个可执行权限
- 4.添加可执行权限,再执行
添加可执行权限![]()
看到通过脚本生成了我们想要的东西,完美执行。
- 5.
添加日志,在执行过程中,我们想知道脚本都干了什么事情再次执行,就发现我们刚才输入的东西打印了出来
【注意】shell脚本为了大家更好的理解,写了说明,执行的时候需要将说明删除,否则会报错
扩展
我们在上面引入系统SDK查找路径,写了很长一大串,我们可以使用变量来进行定义(类似宏定义)
后面我们就可以使用
SYSROOT来代替这个路径了
如果
外部还有其它字符串,我们就需要使用{}
【注意】:=两端不能有空格,否则会默认为其它命令
Dead_Strip
上面我们生成了可执行文件,其中test.m文件代码如下:
我们看到我们只是
引入了TestExample头文件,并没有调用。那么可执行文件中包不包含TestExample.m的代码呢? 下面我们通过命令查看下:发现并
没有TestExample的代码,下面我们将test.m注释的代码打开,再执行以下shell脚本我们看到这次的代码变多了,而且
存在TestExample.m的代码。其实可执行文件就是讲.o文件的代码合并到一起,在这里因为test.m使用了TestExample类,在链接的时候就会将TestExample静态库.o文件的代码链接过来,放在一起。Dead_Strip是默认生效的,但是这过程会有些问题
分类问题
前面文章讲过分类实在运行时动态创建的,但是我们进行可执行文件的创建时,项目并未运行,这就有问题了,下面我们通过项目来讲述
- 1.创建
LjOneObject以及LjOneObject+Category分类 - 2.在方法
lj_test调用分类方法lj_test_category
上面说了
分类是在运行时创建的,而Dead_Strip是在链接过程中生效,当它发现lj_test_category方法所在的分类不存在,Dead_Strip将会将这个方法脱掉。下面我们来验证下
验证Dead_Strip脱掉分类方法
项目运行我们需要加一个workspace,这个大家都很熟悉
我们这里说下workspace好处:
- 1.
可重用性。多个模块可以在多个项目中使用。节约开发和维护时间。 - 2.
节省测试时间。单独模块意味着每个模块中都可以添加测试功能。 - 3.
更好的理解模块化思想。下面我们创建一个workspace给这个workspace起名叫TestDeadStrip
想将上面的LjApp加入到我们的workspace中,怎么做呢?
将LjApp加入后,两个项目就会共存,此时我引入头文件,调用方法,此时就会调用分类方法
当我们运行
代码发现会崩溃,告诉我们找不到分类方法lj_test_category
【原因】:就是上面我们说的,在链接的时候Dead_Strip会将分类方法脱掉,不会链接到静态库中
要解决这个问题还是要告诉我们的链接器,哪些方法你不要脱,我后面会补全,下面我们就用到.xcconflg,有三种方法:
- 1.
-all_load
OTHER_LDFLAGS就是通过clang给我们的dyld传递参数的,但是clang并没有-all_load,然后我们通过-Xlinker,来告诉clang,我的-all_load是传给dyld的不是传给你的
运行代码,发现正常打印出结果了
全部链接,问题就是享受不到系统对静态库的优化,此时就使用-ObjC
- 2.
-ObjC
这个就是说
不要剥离OC的代码,其它的代码该怎么处理怎么处理
- 3.
-force_load
就是
指定静态库不要剥离,其它的该怎么进行就怎么进行,后面跟的是静态库所在路径
写到最后
自己在探究的过程中,可能想到哪里就会去写代码,然后在写到文章里,所以有时候并没有一定连贯性,有问题了可以在下面写下来,我们多沟通交流共同进步,下篇文件会说动态库的有关知识