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的方式引入
没有单独命名空间,同名文件冲突
引入方式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 .

例如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

你可能感兴趣的:(cocoapods的静态库和动态库)