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
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容