簡介
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