cocoapods的静态库和动态库

简介

cocoapods在1.4.0推出了static framework,先扒扒历史原因.

dymanic framework原因

在iOS8以前,苹果只允许发布静态库,当然cocoapods只支持静态库,但是在iOS8苹果推出了APP extension的概念,可以对项目进行扩展,感兴趣的可以看APP extension.
因为APP extension和主项目是两个独立的进程,为了共享代码,苹果允许我们创建动态库,即dynamic framework.

swift第三方库

在swift语言日益优化的前提下,我们想要进行项目swift化,但是在Xcode 6.0 Beta 4的 Release Notes 中,可以找到这句话:
Xcode does not support building ``static libraries that include Swift code. (17181019)

动态库导致的static library报错

看了上面的原因,你会问pod直接使用动态库不就好了,但是对于pod来说,有这么几个问题

  • 包含静态库报错
    The 'xxx' target has transitive dependencies that include static binaries

  • 动态库不能依赖静态库

ok,介绍完历史原因,我们继续看,在讲解适配前,先了解几个概念.

基础介绍

在OC环境下,绝大多数项目都使用cocoapods进行第三方库的管理,pod可以实现依赖管理,版本控制等功能,对于主项目X依赖A,A内部A->B,A->C,B→D,这类的依赖情况,主项目只需要引入A,在安装时就会检测其他的依赖pod是否存在,不存在进行安装.

pod的管理,使得项目中同一类的库只存在一份,cocoapods的项目可以静态库 动态库二选其一,关于这两种的区别下面会做详细解释

默认使用静态库管理,如果想改为动态,需要在podfile内部添加use_frameworks!字段,该字段告诉pod,使用框架的方式,安装和管理第三方库

静态库不能包含swift文件,pod将第三方编译为static library,不能支持swift语言,新版的改为了framework的形式,下面介绍library和framework的区别.

library和framework

library仅能包含编译后的代码,即.a文件,不能包含其他的资源文件.

但是我们封装的第三方库,有时需要包含.h文件,.nib文件,图片,文档扥g

framework可以包含以上所有类型.且支持包含swift代码.

framework支持iOS8以后,而static library可以追溯到iOS6.

由于 iOS 的沙盒机制,自己创建的 Framework 和系统Framework 不同,App 中使用的 Framework 运行在沙盒里,而不是系统中.每个 App 都只能用自己对应签名的动态库,做不到多个 App 使用一个动态库

区别总结

动态库和静态库的区别如下

动态库 静态库
命名空间 有单独的命名空间,不同库同名文件不会冲突
使用import<XXX/xxx.h>的方式引入
没有单独命名空间,同名文件冲突
引入方式import"xxx.h"
加载时机 在启动时加载,加载时间较长 构建时加载
依赖关系 可以依赖动态库,不能依赖静态库 可以依赖动态库和静态库
是否能使用swift 可以包含swift文件 在cocoapods1.4.0之后,可以使用use_framework!的方式包含swift文件
framework支持static_framework

原理分析

上面的总结我们知道静态库在程序启动时被加载,动态库在使用时被加载

那么这些区别原理何在呢,下面分析下几个概念:

编译,目标文件,符号表,链接

编译: 编译器生成机器代码,生成目标文件.

目标文件包含两种符号表: 1.文件转换后的符号(名称和方法的地址及偏移量) 2.未确定的符号(需要在链接阶段才能解析完成的机器代码)

目标文件包含名为"main"的符号,可以将代码块加载进RAM运行.并将"main"作为符号表的运行入口的初始位置

链接: 将我们的各种目标文件加上一些第三方库,和系统库链接为可执行文件

链接主要决议符号,也就是变量函数等的地址

  • 若符号来⾃静态库(本质就是.o 的集合包)或 .o,将其纳⼊链接产物,并确定符号地址

  • 若符号来⾃动态库,打个标记,等启动的时候再说---交给 dyld 去加载和链接符号

于是链接加装载就有了不同的情况

Load 装载:将库⽂件载⼊内存

  • Static Loading:启动时

  • Dynamic Loading:启动后(使⽤时)
    Link 链接:决议符号地址

  • Static Linking:构建(链接)时

  • Dynamic Linking:运⾏时(启动时或使⽤时)

静态,共享和动态库

静态库只是目标文件的集合.静态库只是为了方便处理大量文件.链接器只选取需要的文件并将它们写入最终代码块,这使得静态链接程序很大.( This makes statically linked programs pretty large.)

共享和动态库只需要被系统加载一次.然后使用该库的工程只需要对其进行引用即可.共享和动态库有两种创建方式

1.全量的链接对象文件,包含大量的可被调用的符号表(真实的库代码)

2.通过"stub"对象文件,包含可调用方法的映射表(jump table)

通过动态库链接时,stub对象文件是通过类似静态库的形式被加载到程序中的.但是方法只是加载了方法声明.

当程序使用动态库加载时,系统需要额外链接存储在RAM中的共享库.在加载系统共享库的stub文件时有个实现技巧.有两种方式可以实现加载系统共享库,1.系统拦截调用,进入系统,修改项目地址的上下文,转换到共享库,工作量很大.另一种方式,将静态库映射到运行程序通过虚拟内存管理的地址空间,这使得共享库对于多个项目来说,只是项目的一部分,虽然只在内存中短暂存在.

这样的话,代码被共享,但是每个程序的堆栈由自己管理,使得各个程序员直接完全独立.

结果

静态库:稳定,但是占用内存空间.

动态库:从系统加载代码,共享代码节约空间,但是可以会导致运行时的错误,且不易定位和修复.

动态库详解

苹果官方关于动态库的描述:

动态库相比静态库,减少了app可执行文件的大小.并且可以只在使用时,按需加载而不是在启动时加载.这个特性减低了启动时间,并且更优秀的利用了内存.

动态库不能依赖静态库

启动时间过长解决办法

  1. 第三方框架swift-staticlibs,集成的为动态库,在构建阶段,转为静态库加载的形式,这样做的原因:

    1. Xcode的static library不能包含swift

    2. 动态库启动时间过长

  2. 使用static framework的方式,下面会做介绍

静态库详解

我们可以在Build Setting里面通过Mach-O Type查看target的动态或者静态状态

[图片上传失败...(image-28b016-1519821296948)]

cocoapods1.4.0对于static framework的支持

static framework

pod在1.4.0之后提供了静态框架的特性.过去的ues_framework!只能发布动态库,现在可以发布静态的框架.这一特性解决了过去动态框架不能依赖静态库的弊端.现在的静态framework也可以依赖静态库,也可以依赖通过vendored_frameworks发布的第三方框架.

补充,vendored_frameworks和vendored_library是在podspec文件内使用的属性,用法是声明包含的第三方framework和library.

背景

    1. static framework和library有什么区别呢? framework是对于library,头文件和资源等内容的封装.library可以是动态或者静态的,静态库在构建时期链接,但是动态库是在运行时才进行加载.
    2. 动态库不能依赖静态库是因为静态库不需要在运行时再次加载,如果多个动态库依赖同一个静态库,会出现多个静态库的拷贝,而这些拷贝本身只是对于内存空间的消耗.
    3. 另一个历史原因是,过去很多库是通过包含静态库的vendored_framework形式发布的.
    4. 在1.4.0之前,资源只能通过动态库的方式构建,所以不能依赖vendored_framework的库.而且对于vendored_framework的二进制库,无法在转换成资源pod时仍保持动态性

以上原因,使得pod在1.4.0提供了静态框架的支持.用法简单,只需要在podspec文件内,声明如下即可

s.static_framework = true

限制

所有swift库需要保持一致的版本,包含Swift文件的framework,必须指定swift的版本号,pod之后提供了新特性pod制定swift版本范围


`Pod::Spec.``new` `do` `|s|`

`s.name = ``'BanannaLib'`

`s.version = ``'1.0.0'`

`s.swift_version = ``'>= 3.2'`

`s.source_files = ``'**/*.swift'`

`end`

支持包含swift文件

在创建私有pod时,如果项目中包含swift文件,需要在podfile内部添加use_framework!字段,如果不添加会报以下错误


[!] Pods written in Swift can only be integrated as frameworks; add `use_frameworks!` to your Podfile or target to opt into using it. The Swift Pod being used is: erp-boss-common-ios

如果框架已经声明了static_framework = true,则可以包含swift文件,且可以依赖其他的静态库.

苹果官方的Xcode9发布文档有以下说明Xcode release文档


Xcode supports static library targets which contain Swift code. Debugging applications that use Swift static libraries may require a complete set of build artifacts that are in their original location. (``33297067``)`

Xcode支持包含swift代码的静态库项目.

tips:

包含.a的static library,可以用lipo查看.a库所支持的架构.


lipo -info libTestLib.a 

Architectures in the fat file: libTestLib.a are: armv7 i386 x86_64 arm64

静态库报错

错误

在我们使用use_frameworks!的时候,会遇到类似于下面的错误提示,引起这种提示的原因,和各种情况,下面分析一下.


[!] The 'Pods-testDynamic_Example'` `target has transitive dependencies that include static binaries: (/Users/zhaoyanan/Documents/projects/pod/testDynamic/Example/Pods/SAKC-Ares/lib/libcares_iOS.a)

boss->A->B->SL(static library)

boss为主项目

SL为包含.a的静态库

boss->SL,没有问题

在B内增加static_framework = true, 可以解决boss->B->C问题

对于boss->A->B->C的情况,如果A,A',A''都依赖了B,需要保证他们依赖的方式相同,即都不指定版本号,都在都指定特定的版本号,或者都指定相同的范围,声明不同,则会报错.

s.static_framework = true, s.subspec不需要再设置

其他方法

有一种做法,可以作为参考,没有测试,对于A->B->C-SL的情况十是否使用也不可知,感兴趣的可以研究下


A库依赖B库为例,B库中有一个静态库libB.a :

在A库中修改.podspec :

s.pod_target_xcconfig = {

'FRAMEWORK_SEARCH_PATHS'`=>'$(inherited) $(PODS_ROOT)/Crashlytics',

'OTHER_LDFLAGS'         => '$(inherited) -undefined dynamic_lookup'

}, 

然后在Podfile中添加hook:

pre_install do |installer|

# workaround ``for` `https:``//github.com/CocoaPods/CocoaPods/issues/3289

def installer.verify_no_static_framework_transitive_dependencies; end

end

新特性

在pod1.5.0之后,安装包含swift第三方库的时候,不限制必须在podfile内声明use_frameworks!.但是,如果swift库依赖OC库,就需要在OC库内允许modular headers

Modular Headers

CocoaPods在创建之初,就致力于封装尽可能多的第三方库.pod管理了第三方库的头文件搜索路径(header search paths).pod允许任意pod之间的相互引用,不需要考虑命名空间,不用制定import <nameSpace/fileName>.

例如B库使用#import"A.h"的,pod会配置对应的build setting来保证这种引入的可行.但是如果在其他库内增加了module maps,这种引用就会找不到文件.pod尝试自动去管理静态库的module maps,但是因为这样破坏了pod的使用方式,没有进行下去.
说一下 module maps
在XCode的build setting内,Packaging内有以下设置module map的选项

  1. Defines Module (DEFINES_MODULE) :
    如果设置为YES,会认为项目自定义自己的组件,允许项目通过组件的方式引入

  2. Module Map File (MODULEMAP_FILE)
    用来管理LLVM的module map,定义编译器组件结构.如果defines module为YES的时候,如果Module Map File没填,会自动生成.

在pod1.5.0版本中,通过直接import和组件导入都能找到文件.对于pod开发者,可以在pod_target_xcconfig内添加'DEFINES_MODULE' => 'YES'.对于使用者,可以在podfile内添加use_modular_headers!允许直接import和module map.也可以通过:modular_headers => true配置特定的pod.

引用

  1. cocoapods 1.4.0特性汇总
  2. pod关于static framework的支持
  3. swift官方网站
  4. 动态库会导致启动时间太长
  5. 动态库的个数不要多于6个
  6. library VS framework
  7. ios中的库
  8. pod关于swift版本的讨论
  9. swiftweekly,苹果支持Xcode 9 beta 4发布swift
  10. pod制定swift版本范围
  11. 静态库和动态库加载方式详解
  12. 苹果官方动态库文档
  13. cocoapods原理总结
  14. dylib浅析
  15. 动态库,在构建阶段,转为静态库加载的形式
  16. 组件化-动态库
  17. pod issue static library
  18. import
  19. [pod 1.5.0 不用use_frameworks!]([http://blog.cocoapods.org/CocoaPods-1.5.0/]
  20. deep-dive-into-swift-frameworks

推荐阅读更多精彩内容