简介
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可执行文件的大小.并且可以只在使用时,按需加载而不是在启动时加载.这个特性减低了启动时间,并且更优秀的利用了内存.
动态库不能依赖静态库
启动时间过长解决办法
-
第三方框架swift-staticlibs,集成的为动态库,在构建阶段,转为静态库加载的形式,这样做的原因:
Xcode的static library不能包含swift
动态库启动时间过长
使用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.
背景
- static framework和library有什么区别呢? framework是对于library,头文件和资源等内容的封装.library可以是动态或者静态的,静态库在构建时期链接,但是动态库是在运行时才进行加载.
- 动态库不能依赖静态库是因为静态库不需要在运行时再次加载,如果多个动态库依赖同一个静态库,会出现多个静态库的拷贝,而这些拷贝本身只是对于内存空间的消耗.
- 另一个历史原因是,过去很多库是通过包含静态库的vendored_framework形式发布的.
- 在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的选项
Defines Module (DEFINES_MODULE) :
如果设置为YES,会认为项目自定义自己的组件,允许项目通过组件的方式引入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.
引用
- cocoapods 1.4.0特性汇总
- pod关于static framework的支持
- swift官方网站
- 动态库会导致启动时间太长
- 动态库的个数不要多于6个
- library VS framework
- ios中的库
- pod关于swift版本的讨论
- swiftweekly,苹果支持Xcode 9 beta 4发布swift
- pod制定swift版本范围
- 静态库和动态库加载方式详解
- 苹果官方动态库文档
- cocoapods原理总结
- dylib浅析
- 动态库,在构建阶段,转为静态库加载的形式
- 组件化-动态库
- pod issue static library
- import
- [pod 1.5.0 不用use_frameworks!]([http://blog.cocoapods.org/CocoaPods-1.5.0/]
- deep-dive-into-swift-frameworks