基於CocoaPods的組件化原理及私有庫實踐

輪子爲何會存在

智人能在殘酷的進化大戰中存活下來,緣由之一就是智人懂得將知識沉澱成外物,輔助彼此之間的合做,從而使得整個羣體產生了規模效應,即1+1>2的效果。 從一個角度上說,石器時代是基於石器的組件化的時代,由於老張家的石矛(或其它石頭利器)借給了老王,同樣能夠拿去狩獵。要想實現這個目的,必定要保證:html

  1. 石矛足夠鋒利。否則冒然拿着石矛去找野獸就變成了給野獸送夜宵。
  2. 石矛容易使用。若是是石矛很是重或者難以抓起,也很難讓人使用。

image.png

一種觀點認爲,信息時代是基於軟件構建起來的,由工程師不斷貢獻智力和體力,從而產生價值的時代。產品需求就好像前文說到的獵物,完成需求相似於成功捕殺獵物,而產品逾期就比如被獵物吃掉。所以在這個時代,也須要一些能夠好用且容易使用的功能代碼段,方便程序員拿來快速實現需求,就比如遠古時代的能夠複用的石矛。製做這種功能代碼段的過程叫作組件化,這種方法帶來的產出叫作組件,俗稱輪子。ios

上古時代的輪子

從本質上說,組件是經過庫的方式來進行封裝從而提供給開發者使用。而庫,就是一種組織一個或多個文件的方式。在 iOS 8 以前,iOS 只支持以靜態庫的方式來使用第三方的代碼。c++

靜態庫

靜態庫,在iOS中會被打包成.a文件,配合.h頭文件一塊兒能夠完成功能的調用。可是在在概念上,靜態庫是一種All In One的設計思路,由於依賴靜態庫的代碼會把靜態庫徹底連接到App的可執行文件中。也就是說,靜態庫是在編譯器被連接到App中的,所以若是多個App都引用了同一個靜態庫,則每一個App都會把這個靜態庫連接一份,這其實浪費了內存。 固然,靜態庫的缺點不止於此。在使用靜態庫時,必須手動一個個連接它依賴的外部庫,例如早期微信支付SDK的靜態庫接入方法中,必需要手動連接上:git

SystemConfiguration.framework, 
libz.dylib, 
libsqlite3.0.dylib, 
libc++.dylib,
Security.framework, 
CoreTelephony.framework,
CFNetwork.framework
複製代碼

有沒有一種須要輪流背誦蒸羊羔、蒸熊掌、蒸鹿尾兒、燒花鴨、燒雛雞、燒子鵝、滷豬。。。的既視感。 並且,靜態庫的特色致使了App每次啓動時都要從新加載靜態庫的內存,沒法控制加載時機,並且每次啓動都須要從新加載靜態庫,致使二次加載時間沒法被優化。 大部分時候,還須要在Other Linker Flags裏填入Objc -all_load來確保靜態庫正常工做。 好吧,聽起來靜態庫很難用。 咱們都知道,後期iOS支持了動態庫。那動態庫是否是就能完美解決問題了呢?程序員

動態庫

動態庫,大部分會被打包成.tbd文件或者.dylib文件。不一樣於靜態庫在編譯期連接到App,動態庫是在運行時連接到App的,所以它有了三個好處:github

  • 按需加載,何時須要運行何時加載,提升了啓動app的效率
  • 由於存在多個app使用同一個動態庫的狀況,所以一旦某個動態庫被加載到內存中,下一個app使用時無需再次耗費內存加載此動態庫,你們公用一個動態庫。
  • 由於動態庫不須要參與編譯過程,所以不會產生連接時符號衝突的問題。

不過,蘋果對動態庫的徹底支持僅停留在系統的動態庫上,例如UI.framework,對於第三方的動態庫,仍是須要embed到系統中。早期的一些熱更新框架,例如JSPatch鑽了漏子經過dlopen來進行熱更新,不過很快被禁掉了。 不過,若是是企業證書,仍是能夠在本身的app裏靈活的加載第三方動態庫的。算法

Framework

在解釋靜態庫和動態庫的過程當中,我並無提framework的字眼。有些開發者以爲framework文件就是動態庫,其實並不許確。 咱們提到的framework,指的是.framework文件,這既不必定是靜態庫,也不必定是動態庫。實際上這是一種打包方式,將Header(頭文件)、Binary(二進制代碼文件)和bundle(資源文件)一塊兒打包,方便開發者進行接入和調用。 所以framework究竟是靜態庫仍是動態庫,取決於Binary文件(Mach-O文件)究竟是靜態庫仍是動態庫。sql

痛點

「老一輩」的iOS開發都會記得手動引入靜態庫時,那無止境的編譯錯誤。我簡單總結一下,若是手動引入靜態庫,須要:xcode

  • 將靜態庫和頭文件引入工程
  • 添加各依賴庫(不一樣版本下可能略有不一樣)
  • 修改Other_linker_flags,例如設置-ObjC,-fno-objc-arc等參數
  • 祈禱
  • 編譯,若是出問題,從第一步進行檢查
  • 若是沒有問題,將來要手動管理更新

程序員的創造力不少時候來源於「懶」,終於,CocoaPods橫空出世,今後開啓了一行命令行完成模塊集成的時代!bash

CocoaPods

簡介

CocoaPods是iOS平臺當前最流行的包管理工具,能夠將它理解爲一個能夠自動部署到項目的組件池,而對應的podfile文件就至關於請求組件的Request。當組件下載到工程後,cocoaPods會自動完成組件集成到現有項目的工做,並完成修改.xcodeproj文件和建立.xcworkspace文件。最終將全部組件統一打包成Pods.framework靜態庫,供項目使用。

在CocoaPods中,會存在如下幾種文件:

  • podspec Pod的描述文件,通常來講表徵你的項目地址,項目使用的平臺和版本等信息
  • podfile 用戶編寫的對於指望加載的pod以及對應Target信息
  • podfile.lock 記錄了以前pod加載時的一些信息,包括版本、依賴、CocoaPods版本等
  • mainfest.lock 記錄了本地pod的基本信息,其實是podfile.lock的拷貝 大部分開發者最熟悉的cocoaPods指令就是pod install,那具體在執行pod install時發生了什麼呢?

pod install 運行原理分析

當咱們運行pod install時,會發生:

  • 分析Dependency。 對比本地pod的version和podfile.lock中的pod version,若是不一致會提示存在風險
  • 對比podfile是否發生了變化。 若是存在問題,會生成兩個列表,一個是須要Add的Pod(s),一個是須要Remove的Pod(s)。
  • (若是存在remove的)刪除須要Remove的Pods
  • 添加須要的Pod(s)。 此時,若是是常規的CocoaPods庫(若是基於Git),會先去:
    • Spec下查找對應的Pod文件夾
    • 找到對應的tag
    • 定位其Podspec文件
    • git clone下來對應的文件(根據具體協議的不一樣,這裏還可能存在如下幾種方式的download:Bazaar、Mercurial、HTTP、SCP、SVN)
    • copy到Pod文件夾中
    • 運行pre-Install hook
  • 生成Pod Project
    • 將該Pod中對應文件添加到工程中
    • 添加對應的framework、.a庫、bundle等
    • 連接頭文件(link headers),生成Target
    • 運行 post-install hook
  • 生成podfile.lock,以後生成此文件的副本,將其放到Pod文件夾內,命名爲manifest.lock (若是出現 The sandbox is not sync with the podfile.lock這種錯誤,則表示manifest.lock和podfile.lock文件不一致),此時通常須要從新運行pod install命令。
  • 配置原有的project文件(add build phase)
    • 添加了 Embed Pods Frameworks
    • 添加了 Copy Pod Resources

其中,pre-install hook和post-install hook能夠理解成回調函數,是在podfile裏對於install以前或者以後(生成工程可是還沒寫入磁盤)能夠執行的邏輯,邏輯爲:

pre_install do |installer| 
    # 作一些安裝以前的hook
end

post_install do |installer| 
    # 作一些安裝以後的hook
end
複製代碼

CocoaPods第三方庫下載邏輯

CocoaPods的下載流程

  • 首先,CocoaPods會根據Podfile中的描述進行依賴分析,最終得出一個扁平的依賴表。 這裏,CocoaPods使用了一個叫作 Milinillo 的依賴關係解決算法。簡單說就是使用了回溯法來整理出全部第三方庫的一個依賴列表出來,聽說是CoocaPods的開發工程師原創的算法,在解決問題上應該是夠用,可是貌似若是第三方庫複雜的時候會有性能問題。這裏美團技術團隊對此有專門的優化,詳情請見 美團外賣iOS多端複用的推進、支撐與思考
  • 針對列表中的每一項,回去Spec的Repo中查看其podSpec文件,找到其地址
  • 經過downloader進行對應庫的下載。若是地址爲git+tag,則此步驟爲git clone xxxx.git 注意,此時必需要保證須要下載的pod版本號和git倉庫的tag標籤號一致。

全部依賴庫下載以後,便進入了和Xcode工程的融合步驟。

Xcode工程有什麼變化

Xcode工程上有什麼變化

在cocoaPods和Xcode工程進行集成的過程當中,會有有如下流程

  • creat workspace 建立xcworkspace文件。其實xcworkspace文件本質上只是xcodeproject的集合,數據結構以下:
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "group:Demo/Demo.xcodeproj">
   </FileRef>
   <FileRef
      location = "group:Pods/Pods.xcodeproj">
   </FileRef>
</Workspace>
複製代碼
  • create group 在工程中建立group文件夾,邏輯上隔離一些文件

  • create pod project & add pod library 建立pod.xcodeproject工程,而且將在podfile中定義的第三方庫引入到這個工程之中。

  • add embed frameworks script phase 添加了[CP] Embed Pods Frameworks,相應的,多了pods_xxx的group,下列xxx.framework.sh,來完成將內部第三方庫打包成.a靜態庫文件(在Podfile中若是選擇了!use_frameworks,則此步驟會打包成.framework)

    [CP] Embed Pods Frameworks

  • remove embed frameworks script phase 若是本次podfile刪除了部分第三方庫,則此步驟會刪除掉不須要的第三方庫,將其的引用關係從Pod.xcodeproject工程中拿走。

  • add copy resource script phase 若是第三方庫存在資源bundle,則此步驟會將資源文件進行復制到集中的目錄中,方便統一進行打包和封裝。相應的,會添加[CP] Copy Pods Resources腳本。

    [CP] Copy Pods Resources

  • add check manifest.lock script phase 前文提到過,manifest.lock實際上是podfile.lock的副本。此步驟會進行diff,若是存在不一致,則會提示著名的那句The sandbox is not sync with the podfile.lock錯誤。

  • add user script phase 此步驟是對原有project工程文件進行改造。在運行過pod install後,再次打開原有工程會發現沒法編譯經過,由於已經作了改動。

    • 首先,添加了對Pod工程的依賴,具體爲引用中多了libPods_xxx.a文件。此步驟的.a文件(或者.framework文件)爲上述步驟中xxx.framework.sh打包出來的文件,也就是說,cocoaPods會把全部第三方的組件封裝爲一個.a文件(或者.framework文件)!

      靜態文件引入

    • 創建了Pods的group,內含pods-xxx-debug.xconfig和pods-xxx.release.xconfig文件。這兩個文件是對應工程的build phase的配置。相應的,主工程的Iinfo->Configurations的debug和release配置會對應上述兩個配置文件。

      Configurations

    • 上述兩個配置都作了什麼?包括: Header_search_path,指向了Pod/Headers/public/xxx,添加了Pods文件編譯後的頭文件地址 Other_LDFLAGS,添加了-ObjC等等 一些Pods變了,例如Pods_BUILD_DIR等

至此,原有xcode工程和新建的Pod工程完成了集成和融合。

好了,cocoaPods的好處和原理已經介紹的差很少了。大部分時間,咱們經過引用github上的組件就夠用了。可是有時候處於業務須要,咱們須要來實現私有Pod庫。因此接下來咱們來介紹下如何在公司內網來實現一個私有庫,實現一個私有組件。

利用CocoaPods實現私有組件

準備工做

  • 安裝好XCode
  • 配置好CocoaPods,而且能夠pod update 以及 pod install 成功
  • 已經得到CocoaPods的Repo的地址,以及對應pod的Git地址(這裏以git.xxx.com上申請的repo爲例)
  • 涉及到的全部操做,請儘可能在Terminal中進行,包括CocoaPods的相關操做(不要在CocoaPods官方客戶端操做)
  • 本文涉及到的Demo,能夠去git.xxx.com/XXX_SPA_XXX…去圍觀

私有Spec Repo

所謂Spec Repo,就是Pods的索引。一旦在podfile中設置source爲某個私有repo的git地址,在進行pod update的時候就會去這個repo中進行檢索,若是檢索到對應的pod,會讀取該Pod的podspec從而進行安裝。 一個Spec Repo的目錄結構以下:

image.png

以後咱們去git.xxx.com上新建一個相應的Repo地址,以後添加repo到本地,該repo地址是爲了後面提交podspec使用。

# pod repo add [Private Repo Name] [GitHub HTTPS clone URL]
pod repo add XXXCocoaPodsRepo git@git.xxx.com:XXX_SPA_XXX/iOS_CocoaPods_Repo.git
複製代碼

成功後能夠進入~/.cocoapods/repos目錄下查看XXXCocoaPodsRepo這個目錄了。

建立並Clone目標Pod地址

這裏,咱們以HelloXXXPod爲例。 去git.xxx.com上去新建項目,以後獲取地址,爲:

git@git.xxx.com:XXX_SPA_XXX/HelloXXXPod.git
複製代碼

此時clone到本地,命令爲:

git clone git@git.xxx.com:XXX_SPA_XXX/HelloXXXPod.git
複製代碼

建立Pod項目工程文件(源碼方式)

這裏建議經過CocoPods的官方命令來進行Pod項目的建立,以測試項目HelloXXXPod爲例,命令以下:

pod lib create HelloXXXPod
複製代碼

不出意外地話,會提問你六個問題(cocoaPods v1.5.3版本):

1.What platform do you want to use? [ iOS / macOS ]

2.What language do you want to use? [ Swift / ObjC ]

3.Would you like to include a demo application with your library? [ Yes / No ]

4.Which testing frameworks will you use? [ Specta / Kiwi / None ]

5.Would you like to do view based testing? [ Yes / No ]

6.What is your class prefix?
複製代碼

分別解釋一下

  • What platform do you want to use?? [ iOS / macOS ] 問組件化應用在哪一個平臺上,通常咱們選iOS

  • What language do you want to use? [ Swift / ObjC ] 使用何種語言,能夠根據項目是OC仍是Swift自行選擇

  • Would you like to include a demo application with your library? [ Yes / No ] 問是否須要一個Demo工程,方便調試Pod。若是是第一次作組件化,建議選Yes,方便pod的調試

  • Which testing frameworks will you use? [ Specta / Kiwi / None ] 問是否須要UT測試框架,可選擇Specta和Kiwi,或者選擇不要。

  • Specta是OC的一個輕量級TDD/BDD框架,參考github/specta

  • Kiwi是一個iOS的一個BDD框架,能夠簡單地部署和使用。github/kiwi UT測試框架若是要選擇的話,建議選擇Kiwi,能夠參考我以前寫的調研kiwi上手體驗 本次的Demo,暫時選None

  • Would you like to do view based testing? [ Yes / No ] 若是上一步選擇了Specta ,這步會生成一部分有利於作自動化測試的邏輯和代碼

  • What is your class prefix? 這裏能夠指定你的項目前綴,這樣在new一個類時會自動加上前綴

以後咱們運行pod install,生成的文件目錄樹結構以下:

$ tree HelloXXXPod -L 2

HelloXXXPod
├── Example
│   ├── Build
│   ├── Podfile
│   ├── Podfile.lock
│   ├── Pods
│   ├── Tests
│   ├── helloXXXPod
│   ├── helloXXXPod.xcodeproj
│   └── helloXXXPod.xcworkspace
├── LICENSE
├── README.md
├── _Pods.xcodeproj -> Example/Pods/Pods.xcodeproj
├── helloXXXPod
│   ├── Assets
│   └── Classes
└── helloXXXPod.podspec
複製代碼

開發

這時候能夠在剛纔生成的Example工程內作開發,這時候記得把新建的代碼放到Classes目錄下。若是有圖片資源,建議放到Assets下。

開發、調試完成以後,就能夠去編輯podspec文件了。按如下方式來修改,不明白的字段請參考官方文檔

這裏給出本次Demo的podspec供各位參考:

Pod::Spec.new do |s|
  s.name             = 'helloXXXPod'
  s.version          = '0.1.0'
  s.summary          = 'A short description of helloXXXPod.'
  s.description      = <<-DESC
TODO: Add long description of the pod here.
                       DESC

  s.homepage         = 'https://git.xxx.com/XXX_SPA_XXX/HelloXXXPod'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { 'nimomeng' => 'nimomeng@tencent.com' }
  s.source           = { :git => 'git@git.xxx.com:XXX_SPA_XXX/HelloXXXPod.git', :tag => s.version.to_s }

  s.ios.deployment_target = '8.0'
  s.source_files = 'helloXXXPod/Classes/**/*'
end

複製代碼

其中,注意修改這幾個字段:

  • s.name
  • s.homepage
  • s.source (很是重要)
  • s.source_files (若是不放在Classes下,記得在這裏指定文件目錄)

本地調試

若是是經過pod lib create命令建立的Pod,會在Example中自動配置好該pod的本地調試腳本,以下:

use_frameworks!

platform :ios, '8.0'

target 'helloXXXPod_Example' do
  pod 'helloXXXPod' :path => '../'

  target 'helloXXXPod_Tests' do
    inherit! :search_paths

    
  end
end
複製代碼

其中,pod 'helloXXXPod' :path => '../'的含義是說,在上層目錄來下載helloXXXPod這個pod。這是本地調試Pod的一種。 一樣的,能夠實現相似方式調試的方法,還有經過:podspec命令來指定,指定pod所在的podspec文件位置便可

其中,path語法精確到目錄便可;podspec語法必需要精確到文件。

設置好podfile以後,在Example文件下執行pod install,則能夠發現新的文件已經出如今項目工程的pods文件夾之下了。

image.png

注意,經過path語法進行更新後,Pod中代碼並不在Pod文件夾中,而是在一個叫 Development Pods中。

開發完成,須要本地驗證podspec,確保其有效:

pod lib lint helloXXXPod.podspec
複製代碼

同步到Git上

以後要作的就是把庫同步到Git上去了。這時候須要去git.xxx.com上創建一個對應的倉庫,例如:

http://git.xxx.com/XXX_SPA_XXX/HelloXXXPod.git (替換爲本身的實際git地址)
複製代碼

而後將代碼同步到此Git上。

git add .

git commit -m "Init"

git remote add origin http://git.xxx.com/XXX_SPA_XXX/HelloXXXPod.git(替換爲本身的實際git地址)

git push --set-upstream origin master

複製代碼

podSpec文件須要版本控制信息,因此咱們要打一個Tag.

git tag -m "first demo" 0.1.0

git push --tags
複製代碼

向Spec Repo提交podspec

在執行本歩以前,確保最新代碼已經提交到了Git上,且已經打好了tag.

向Spec Repo提交podspec的命令:

pod repo push XXXCocoaPodsRepo HelloXXXPod.podspec --allow-warnings
複製代碼

在通過三輪的用戶校驗以後,提交成功!這時候咱們去~/.cocoapods/repos/XXXCocoaPodsRepo中查看,咱們的的podspec已經在裏面了!

此時經過pod search HelloXXXPod 已經能夠查到了!

image.png

最後,爲了保證本地的repo已經被更新,運行pod update來更新repo

如何在外部項目中使用

咱們能夠在想要使用的項目中的Podfile里加入以下代碼:

pod 'helloXXXPod'
複製代碼

便可。 固然,因爲咱們的是私有CocoaPods庫,所以最好告訴系統這個庫的source在哪裏,所以在Podfile文件上部也請加上Spec Repo的git地址。同時,爲了確保公共的cocoaPod能夠被正常下載,請添加外部CocoaPod的庫:

# For inner pods
source 'git@git.xxx.com:XXX_SPA_XXX/iOS_CocoaPods_Repo.git'

# For public pods
source 'https://github.com/CocoaPods/Specs.git'

複製代碼

整個的Podfile文件看起來是這樣的:

use_frameworks!

platform :ios, '8.0'

# source 'git@git.xxx.com:XXX_SPA_XXX/iOS_CocoaPods_Repo.git'

# For public pods
source 'https://github.com/CocoaPods/Specs.git'

target 'helloXXXPod_Example' do
  pod 'helloXXXPod'

  target 'helloXXXPod_Tests' do
    inherit! :search_paths
	
  end
end

複製代碼

以後運行pod install 便可安裝對應的Pods

驗證

咱們能夠複用Example項目,只不過此次再也不經過:path命令或者:podspec命令來作本地調用,而是徹底使用安裝外部pod的方式,即:

pod 'helloXXXPod'
複製代碼

注意:雖然pod已經推送到線上,可是本地必定要先更新pod的repo,否則仍是沒法找到最新的pod。確保先作pod update操做。

Example項目中,咱們調用在Pod中寫好的方法,查看是否輸入對應的log便可驗證:

image.png

至此,Pod建立完成。

常見問題

  • 若是pod中用到framework,應該在哪裏添加?

    若是pod中用到framework,如AVFoundation,直接在podspec文件中添加s.frameworks = ‘AVFoundation’或者s.frameworks = [‘AVFoundation’,'MapKit'],而不該該添加在項目的Link Binary With Libraries下面。

  • 怎麼取更新私有 pod?

    更新私有pod的過程和建立pod的步驟一致,可是要記得在更改代碼後要記得必定從新run一下aggregate,更改podspec裏的s.version(由於tag不能重複提交), 從新pod repo push

  • 若是出現這個錯誤怎麼辦:

[!] An unexpected version directory `Assets` was encountered for the `/Users/nimo/.cocoapods/repos/xxxx` Pod in the `xxxx` repository.
複製代碼

這個錯誤,請查看:

  • podspec 是否未上傳到服務器
  • Podfile的source地址是不是Spec Repo的地址,而不是具體某一個Pod的地址。

參考文章

相關文章
相關標籤/搜索