iOS高级进阶系列之-库(下)动态库和静态库项目应用

6,206 阅读12分钟

系列文章:OC底层原理系列OC基础知识系列Swift底层探索系列iOS高级进阶系列

前言

前面文章讲了静态库和动态库,讲的内容都是为了这篇文章做准备,这边我们就聊一下实际SDK开发对静态库和动态库的应用,平时开发也会用到文章讲的内容。

XCFramework

XCFramework简介

  • 1.是苹果官方推荐的、支持的,可以更方便的表示一个多个平台架构分发二进制库的格式。在19年推出
  • 2.需要Xcode11以上支持。(不是iOS系统版本,是Xcode11才有这个功能
  • 3.是为更好支持Mac Catalyst(是为了将iPad上的App更好的移植到ARM芯片的macOS的一个功能)和ARM芯片的macOS。 专⻔在2019年提出的framework的另一种先进格式

和传统的framework相比

  • 1.可以用单个.xcframework文件提供多个平台的分发二进制文件;
  • 2.与Fat Header相比,可以按照平台划分,可以包含相同架构不同平台的文件;
  • 3.在使用时,不需要通过脚本剥离不需要架构体系

动态库合并

写了一个功能名字叫SYTimer,我们将它编译成动态库 image.png

模拟器架构

此时需要编译命令: image.png 点击回车,就会发现项目开始编译,最后在指定文件生成我们需要文件 image.png

编译过程中一直生成.o文件,上面文件就是我们平时Xcode编译的文件

我们展示下包内容,看下里面的东西

image.png

SKIP_INSTALL设置为NO就是为了将这个framework拷贝到这个目录下

真机架构

上面我们已经编译了模拟器架构,下面我们来编译成真机架构 image.png

红框就是不同点,一个是分发平台,一个名字,点击回车

image.png

此时我们看到文件夹中已经有两个文件,一个是模拟器,一个是真机,下面我们来进行合并

合并

在制作SDK过程中,我们需要提供的SDK需要即支持真机,又需要支持模拟器!此时我们需要量两个动态库合并,此时我们常用的就是lipo命令 image.png

上面就是lipo的解释:创建或者操作一个文件

胖二进制

我们先说下胖二进制胖二进制就是多个架构打包到一起,但是就会有一个问题就是打包在一起的架构是不能相同的,下面我们查一下生成的真机SYTimer可执行文件的架构 image.png

我们发现有两个架构分别为arm_v7,arm64

上面我们进行打包的时候,只指定了分发平台,并未指定架构,但是为什么会出现两个呢?

  • 这是因为在打包的时候,会执行Xcode项目中Build settings的设置。 image.png 继续说胖二进制,胖二进制是将各个动态库mach-header放在一起各个库文件也是肩并肩放在一起,并不是真真意义上的合并

合并

下面我们使用lipo命令进行合并 image.png 我们回车后发现报错了,报错的原因就是两个架构都存在arm64 image.png

这个主要是因为,我们在进行模拟器打包时,Build Settings设置的有arm64

如果非要合并,那么我们需要将模拟器的x86架构提取出来,获奖arm64架构删除掉,下面我们来做提取 image.png 提取出来后,我们再做合并 image.png

此时具有多架构的SYTimer就生成了

问题:

  • 1.生成的SYTimer需要重新处理头文件
  • 2.需要包装成一个新的framework
  • 3.需要重新签名 以上问题都需要手动操作 还有其他问题,我们在打包的时候会生成sdYM以及map image.png

dsYM用处应该都懂,而map是如果支持了bitcode,就需要BCSymbolMaps否则即使上传dsYM也无法解析

说完问题,怎么解决,就引出我们的要讲的内容:苹果在19年推出的XCFramework

使用XCFramework

下面我们就来通过命令编译一个XCFramework image.png 按下回车,我们就发现生成了我们需要的XCFramework image.png

  • 我们可以看到相同架构,也是能合并的,而且自动生成相应架构framework,上面我们说的sdYM没有放进来,我们给它放进来 image.png
  • 注意的是-debug-symbols链接文件需要是绝对路径不能使用相对路径 image.png
  • 我们上面说的sdYM和map都给链接了进来 下面我们来使用这个xcframework
  • 1.直接将SYTimer.xcframework拖入项目 image.png
  • 2.引入头文件,创建属性 image.png
  • 3.选择模拟器进行编译 image.png 上面讲了,xcframework根据不同平台选择不同的架构,而不像传统的framework,都包含在内,下面我们验证一下
  • 1.根据模拟器编译成功,会生成一个可执行文件,我们查看下这个可执行文件包内容里的Frameworks,包含的SYTimer.framework的架构 image.png image.png 这个是模拟器架构
  • 2.换成arm64架构进行编译,查看下可执行文件 image.png image.png 这个是真机架构

总结

通过上面我们知道xcframework比lipo命令好很多

  • 1.不用处理头文件
  • 2.不用再去管重复的架构
  • 3.可以把调试符号暴露出来 所以建议大家赶快将xcframework用起来吧

项目实际应用

弱引用

创建一个项目,我们想在这个项目中引入SYTimer.framework image.png 按照我们之前讲的,链接一个framework的三要素

  • 1.指定头文件 -I image.png
  • 2.指定framework文件所在目录 -F image.png
  • 3.指定framework名称 -l image.png 写完后,写代码运行,运行成功了 image.png 下面我们运行项目 image.png 项目报错,很熟悉的错误,这是因为我们并没有告诉framework的具体位置,我们可以将framework拖进项目,但是我不想这么做,我想通过xcconfig来设置@rpath image.png 那么这个设置的@rpath报错的/SYTimer.framework/SYTimer拼接就能找到路径,我们再运行一次,运行成功 image.png

弱引用符号

之前说过弱引用,如果说这个东西在运行的时候没有定义,它有是个弱引用符号,那么链接器自动将它置为null,也就是它可以允许在运行的时候可以为空可以在运行的时候找不到

  • 上面讲如果没有路径运行会报错 image.png
  • 我们可以把SYTimer.framework置为弱引用,这样就不会报错了 image.png
  • 我们看下-weak-framework链接器中的定义 image.png
  • 跟上面解释一致,不再过多叙述,直接运行,项目看会不会报错 image.png 项目没有报错,但是打印为null,此时我们设置起作用的,这有什么好处,当项目某一个库找不到的时候也不会报错
  • 那么此时Mach-o做了什么,我们可以看一下 image.png 此时它cmd不再是LC_LOAD_DYLIB 而是:LC_LOAD_WEAK_DYLIB

链接冲突

我们项目中会遇到这样的情况,引入第三方A,里面有个库叫E,在引入一个第三方B,里面也有个库叫E,此时就会出现冲突,因为引入了两个E,那么此时怎么解决呢?下面我们模拟一下,假设项目中引入两个AFNetworking,我们来链接这两个库 image.png 下面我们创建xcconfig文件,在文件里进行操作 image.png

  • 写完后我们编译,没有问题,下面我们引入头文件进行操作,之后编译也没有问题。 image.png 我们链接了两个相同的静态库,只是名字不同,但是在编译期是没有冲突的,为什么?

因为在链接静态库的时候,默认的是-noall_load,也就是它会进行代码剥离,当找到AFNetworking的时候后面的AFNetworking2不会再链接进去了

  • 如果换成-all_load,再编译 image.png

报错,提示有223个符号冲突,那么怎么解决这个问题呢?

  • 我们看下cocoapod的xcconfig文件,发现OTHER_LDFLAGS有个-ObjC,是指所有的OC代码都会被导入,因为AFNetworking和AFNetworking2都为OC代码!那么此时我们怎么做?我们可以使用-load_hidden image.png

静态库文件可以进行隐藏不导出任何东西 下面就是对xcconfig文件重新写 image.png

  • 运行成功 image.png

动态库链接动态库

  • 1.我们创建一个Fremework叫LJNetworkManager,然后通过cocoaPods导入动态库AFN image.png
  • 2.此时我们在动态库LJNetworkManagerLJAFNetworkingManager.m来引入动态库AFNetworking,在LjNetworkManagerTests调用LJAFNetworkingManager方法 image.png
  • 3.此时编译是没有问题的,但是对LjNetworkManagerTests进行运行报错image.png
  • 4.原因是并没有找到AFNetworking,此时我们看LjNetworkManagerTests的包内容 image.png

它是不存在Frameworks文件夹的,更不会存在AFNetworking

  • 5.如何解决呢?我们可以将AFN绝对路径给到LjNetworkManagerTests image.png
  • 6.此时再运行就会发现都成功了 image.png

但这个问题不能这么解决,所以绝对路径实不可取

  • 7.我们可以通过copy的形式将framework拷贝到包里 image.png

这个是cocoapods中的.sh脚本中代码,意思就是将项目拷贝到相应的目录,这也就是为什么cocoapods管理的动态库都没这样的问题,还有一种简单的解决办法

  • 8.让cocoapods对LjNetworkManagerTests同样导入AFN,然后更新 image.png
  • 9.更新后再运行,我们就发现也成功image.png image.png

拓展

如果我LJNetworkManager想引用LjNetworkManagerTests里的代码(反向依赖)是否可行,之前文章说过dylid在链接的过程中会将所有的导出符号放在一起,只要再运行的时候能够找到,就能正常运行

  • 1.暴露Header image.png
  • 2.导入头文件,在LJNetworkManager引入LJTestAppObject.h,就需要改一下Pods-LjNetworkManager.debug.xcconfig image.png
  • 3.在LJAFNetworkingManager引入LJTestAppObject,发现识别出来了 image.png
  • 4.运行后发现报错,找不到这个符号 image.png

我们前面说过项目只要运行起来,它自己就能找到,怎么让项目运行起来,之前提过-undefined,这里直接写 image.png 此时运行就成功了,但是有个问题,我们随便写的符号也会被忽略掉,不会报错!

  • 5.指定符号-U image.png

此时也会成功

  • 6.验证 image.png

我们看到此时调到LJTestAppObject方法,所以我们上面操作是对的实现反向依赖

动态库链接静态库

还是上面的代码,此时我们将Podfile中引入AFN的use_frameworks!去掉 image.png

  • 1.更新Podfile后,引入的就是AFN的静态库 image.png
  • 2.此时编译不会报错 image.png
  • 3.此时我们LjNetworkManagerTests能否引入静态库AFN,答案是可以的,我们来配置一下 image.png
  • 4.上面设置完就可以了,因为我们链接的是静态库,只需要找到头文件就行了,之前文章说过,这里不再解释。运行时成功的 image.png

拓展

当我给别人提供动态库时,我不想静态库符号暴露出来,怎么做呢?

  • 1.链接器给我们提供了相应的参数来隐藏:-hidden-l image.png
  • 2.此时我们再编译 image.png

报错,未定义的符号!我们通过这种方式控制静态库符号是否展示

静态库链接静态库

还是上面的项目,此时我们将我们创建的库变成静态库,而AFN同样也是静态库 image.png

此时LjNetworkManagerTests使用LJAFNetworkingManager会不会报错?

我们分析一下:LjNetworkManagerTests引入静态库没什么问题,但是组件是个静态库,它引入静态库,需要手动集成才行,我们编译下: image.png

找不到符号,此时就需要我们暴露出去

  • 1.在Build settings image.png
  • 2.此时在编译项目,就成功image.png

组件链接静态库,并没有暴露给LjNetworkManagerTests,所以需要我们手动导入

静态库链接动态库

还是上面的项目,此时我们导入AFN为动态库 image.png

  • LjNetworkManagerTests使用静态库没什么问题,但是要使用动态库就需要将动态库放入LjNetworkManagerTests中才行。编译项目发现项目报错 image.png

未找到符号,原因LjNetworkManagerTests使用LJAFNetworkingManager,而LJAFNetworkingManager使用了动态库AFN,而LjNetworkManagerTests并不知道AFN的位置,故报错

  • 告诉LjNetworkManagerTests需要使用的AFN动态库的位置 image.png
  • 运行项目,不成功 image.png

因为我们的LjNetworkManagerTests不存在AFN的包,这个问题和我们将的动态库链接动态库相似,这里不再说,大家可以自己试试。 上面的LjNetworkManagerTests大家可以看成一个一个新的App,我是为了省事用LjNetworkManagerTests来代替App

拓展

Podfile导入多样库

上面我们讲了动态库静态库间得相互链接,我们知道我们用cocoapods导入AFN是通过use_frameworks!来确定导入静态库还是动态库,现在有个问题,我想在项目中即导入动态库又导入静态库,该怎么做 image.png

比如此时我想让AFN作为静态库导入,而SDWebImage作为动态库导入

  • 在Podfile做如下操作 image.png

简单解释下,就是我们在导入podfile的第三方时,判断如果是AFN就以静态库方式导入,否则就按动态库导入 image.png

通过podfile同时导入多个项目

image.png

MulitProject.xcworkspace基于TestOC而来,而LjNetworkManager带有Podfile的第三方库

  • 我们通过LjNetworkManager的Podfile同时TestOCLjNetworkManager添加依赖库,在LjNetworkManager的Podfile做下面改动: image.png
  • 更新下Podfile image.png

简单总结

XCFramework

  • 1.头文件
  • 2.调试符号
  • 3.相同架构的处理

项目应用

  • 1.weak_import动态库 运行时 -> 位置
  • 2.静态库冲突App -> all_load\ObjC
  • 3.App -> 动态库链接动态库 -> pod\脚本复制 -> rexport动态库
  • 4.App -> 动态库链接静态库 -> 静态库代码不想暴露 -> hidden-l静态库
  • 5.App -> 静态库链接静态库 -> 三要素:1.名称 2.头文件位置 3.包所在位置
  • 6.App -> 静态库链接动态库 -> 编译报错:不知道动态库所在位置,运行报错:动态库@rpath -> pod\脚本复制

写在最后

内容比较多,写了一周左右时间!写了5000多字,写的比较细,都是把自己探索过程记录下来,希望大家能够按着文章去实操一遍,这部分也比较无聊,但是这是高阶必走的路。有什么疑问可以在下面留言,也希望大家多多交流,点赞!

补充

上面写的命令可以通过终端打印:man ld,man nm进行查询