iOS 一步步帶你實踐組件二進制方案


前言

隨着業務的擴展、項目體積的增大,CocoaPods組件庫越來越多,每次重新編譯的時候速度越來越慢,這給我們提出了需要提高編譯速度的需求。

為了提高項目編譯速度,對於大量使用組件化開發的項目組而言,組件二進制化是必然要走的路線,雖然中心思想就是要將各個組件打包成.a二進制庫,但是各個公司可能方案都不太相同,網上的方案也有很多可供選擇,這里我大體總結成以下幾種:

  • 分倉庫管理
  • Carthage管理
  • podspec環境變量(宏管理)
  • podspectag管理(只針對私有庫)

前兩個就不在這里討論了可以看看這篇講解。今天重點給大家分享一下第三和第四種方案的實施,但是目前只能針對私有庫實施,對於一些第三方的公有庫目前沒有什么好的方案(😁 有好方法的同學可以在評論區推薦一下)。

實施

1、創建pod私有庫

😝 如果您對這一塊很了解請跳過這一步直接看第二步

對於私有庫的創建,一般我們會采用pod lib create XXX模板來進行構建(如果還不知道這條命令是干嘛的同學可以先移步了解一下理解CocoaPods的Pod Lib Create

這里我們拿ABC這個項目進行舉例,首先我們執行pod lib create ABC創建ABC的私有庫 CocoaPods會從https://github.com/CocoaPods/pod-template.git下載模板文件,並詢問你一些構建信息,正常填就好了。

作為一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:413038000,不管你是大牛還是小白都歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 大家一起交流學習成長!

以下資料在群文件可自行下載!

推薦閱讀

iOS開發——最新 BAT面試題合集(持續更新中)

[MichaeldeMacBook-Pro:~ michaelwu$ pod lib create ABC
Cloning `https://github.com/CocoaPods/pod-template.git` into `ABC`.
Configuring ABC template.

------------------------------

To get you started we need to ask a few questions, this should only take a minute.

If this is your first time we recommend running through with the guide: 
 - https://guides.cocoapods.org/making/using-pod-lib-create.html
 ( hold cmd and double click links to open in a browser. )

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

復制代碼

一般如果我們構建好了的話工程目錄會類似這樣一個結構:

.
├── ABC
│   ├── Assets
│   └── Classes
├── ABC.podspec
├── Example
│   ├── ABC
│   ├── ABC.xcodeproj
│   ├── ABC.xcworkspace
│   ├── Podfile
│   ├── Podfile.lock
│   ├── Pods
│   └── Tests
├── LICENSE
├── README.md
└── _Pods.xcodeproj -> Example/Pods/Pods.xcodeproj
復制代碼

這里你會發現,CocoaPods已經幫我們創建好了Demo、源文件目錄、Podfilepodspec.gitignore文件等(真是一個貼心的小家伙),而且很規范,Demo文件在Example目錄下

窺視一下podspec文件你就明白了源碼需要指定在./Classes/**/*路徑下

 s.source_files = 'ABC/Classes/**/*'
復制代碼

為了演示效果,我們創建兩個源文件ABC.hABC.m並放入Classes路徑下,同時將默認的ReplaceMe.m刪除

[圖片上傳中...(image-b08f0c-1594108050326-4)]

接着在Example下執行pod install,可以發現ABC.h/m已經導入成功

[圖片上傳中...(image-e75b0-1594108050326-3)]

至此,我們就明白了私有庫的創建過程,需要編寫源代碼需要放入指定目錄下並在執行pod install進行同步

2、創建靜態庫

組件二進制其實指的就是打包成動態庫/靜態庫,由於過多的動態庫會導致啟動速度減慢得不償失,此外iOS對於動態庫的表現形式只有framework,若想做源碼與二進制切換時,引入頭文件的地方也不得不進行更改,例如:

import <ABC.h> // 源碼引用
import <ABCBinary/ABC.h> // 動態庫引用
復制代碼

而打包成靜態庫.a文件(注意不要打包成framework形式)則不需要更改引用代碼,所以綜上所述,我們選擇打包成靜態庫的方式不需修改引用代碼、縮小體積提升編譯速度。

確定目標之后,就是實施了,一般而言我們私有庫都會在遠程托管地址有git倉庫,然后再上傳到指定的私有源(specs)上,那么就會引申出幾個問題:

  • 要不要將靜態庫上傳到git(如果包體積很大會很占用git空間)
  • 怎么做到一套代碼同時管理源碼和二進制
  • 為了能夠調試源碼,如何在源碼及二進制間切換(下一步驟會講到)

針對這幾個問題,一一回答:

3、靜態庫與源碼如何用同一套代碼管理?

其實這個很簡單,我們接着拿ABC這個項目舉例子,進入Example打開我們的ABC.xcworkspace工程,然后創建新的Target為靜態庫,並取名為ABCBinary(一定要取這個名字,后面我會解釋)

File->New->Target->Static Library
復制代碼

此時在Example目錄下會增加剛剛創建的Target文件夾,結構如下:

├── ABCBinary
│   ├── ABCBinary.h
│   └── ABCBinary.m
復制代碼

Xcode默認會幫我們生成兩個文件,我們將.h改名為placeholder.h.m刪除,這里為什么要將.h換成placeholder.h呢?先賣個關子,待會我們再作解釋。

我們把剛才寫的ABC.h/m的源碼拖到ABCBinary中,注意不要勾選Copy items if needed,只做引用即可

[圖片上傳中...(image-d11b04-1594108050325-2)]

[圖片上傳中...(image-a6b089-1594108050324-1)]

之后我們需要到ABCBinaryBuild Setting中指定靜態庫所能運行的最低版本:

Build Setting->Deployment->iOS Deployment Target
復制代碼

並在Build Phases中指定頭文件,將ABC.h拖入Public中,具體步驟:

TARGETS->ABCBinary->Build Phases->New Header Phase
復制代碼

至此我們完成了一套代碼管理二進制與源碼,但有個小細節需要注意:就是如果源代碼有變動需要在XXXBinary文件中重新導入一遍,不然二進制的文件不會自動更新(同學們有好的建議可以評論區討論下)

4、是否需要將二進制上傳至git?

其實git對代碼管理時會將不同的diff做備份(在.git這個文件夾下),但是對於二進制文件來說git就沒用那么友好了,會將二進制的每一次提交都做磁盤備份,以便於隨時版本回滾,倘若我們每次都對私有庫進行更新時都將二進制包傳至git,那么時間久了無疑是對git倉庫空間的一個挑戰(如果你們公司空間足夠大不需要考慮,那么請忽略這一步)

網上有很多針對這個問題給出的解決方案,但都不是很完美,大體上都是說將二進制包單獨傳到另一份靜態資源地址,以此解決git過大問題,不過我覺得沒有解決痛點,能不能不上傳二進制包呢?

結論當然是可以,CocoaPods本地的緩存目錄在

~/Library/Caches/Cocoapods
復制代碼

其實每次我們更新pod庫時,CocoaPods都會先從指定源去拉源代碼再根據該庫的podspec文件指定輸出目標文件,那么我們如果能把靜態庫打包推遲到pod install階段就不需要上傳二進制包到git了,但是如何做到延遲打包呢?

很幸運,CocoaPods提供了針對podspec的預執行腳本,prepare_command(戳我進官網)命令,該命令可以指定相應的腳本在pod install時去執行,那么我們就可以將編譯打包的腳本放入其中,從而完成延遲打包

好了,理論上貌似可行了,實踐出真知啊(😄 絕對不能做一個理論性選手啊),具體怎么做?

首先我們需要一個能一鍵打靜態庫包的腳本(一刀99級那種),帥氣的我這邊已經為大家准備好了,只修改一下PROJECT_NAME即可,拷貝腳本至根目錄並賦予執行權限:

# 當前項目名字,需要修改!
PROJECT_NAME='ABC'

# 編譯工程
BINARY_NAME="${PROJECT_NAME}Binary"

cd Example

INSTALL_DIR=$PWD/../Pod/Products
rm -fr "${INSTALL_DIR}"
mkdir $INSTALL_DIR
WRK_DIR=build

BUILD_PATH=${WRK_DIR}

DEVICE_INCLUDE_DIR=${BUILD_PATH}/Release-iphoneos/usr/local/include
DEVICE_DIR=${BUILD_PATH}/Release-iphoneos/lib${BINARY_NAME}.a
SIMULATOR_DIR=${BUILD_PATH}/Release-iphonesimulator/lib${BINARY_NAME}.a
RE_OS="Release-iphoneos"
RE_SIMULATOR="Release-iphonesimulator"

xcodebuild -configuration "Release" -workspace "${PROJECT_NAME}.xcworkspace" -scheme "${BINARY_NAME}" -sdk iphoneos clean build CONFIGURATION_BUILD_DIR="${WRK_DIR}/${RE_OS}" LIBRARY_SEARCH_PATHS="./Pods/build/${RE_OS}"
xcodebuild ARCHS=x86_64 ONLY_ACTIVE_ARCH=NO -configuration "Release" -workspace "${PROJECT_NAME}.xcworkspace" -scheme "${BINARY_NAME}" -sdk iphonesimulator clean build CONFIGURATION_BUILD_DIR="${WRK_DIR}/${RE_SIMULATOR}" LIBRARY_SEARCH_PATHS="./Pods/build/${RE_SIMULATOR}"

if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi
mkdir -p "${INSTALL_DIR}"

cp -rp "${DEVICE_INCLUDE_DIR}" "${INSTALL_DIR}/"

INSTALL_LIB_DIR=${INSTALL_DIR}/lib
mkdir -p "${INSTALL_LIB_DIR}"

lipo -create "${DEVICE_DIR}" "${SIMULATOR_DIR}" -output "${INSTALL_LIB_DIR}/lib${PROJECT_NAME}.a"
rm -r "${WRK_DIR}"
復制代碼

我們還是拿ABC的項目來接着實踐,拷貝腳本后,先來看一下我們ABC目前的結構:

.
├── ABC
│   ├── Assets
│   └── Classes
├── ABC.podspec
├── Example
│   ├── ABC
│   ├── ABC.xcodeproj
│   ├── ABC.xcworkspace
│   ├── ABCBinary
│   │   └── placeholder.h
│   ├── Podfile
│   ├── Podfile.lock
│   ├── Pods
│   └── Tests
├── LICENSE
├── README.md
├── _Pods.xcodeproj -> Example/Pods/Pods.xcodeproj
└── build_lib.sh
復制代碼

可以看到最下面多了一個build_lib.sh腳本(就是剛剛拷貝的那個腳本),另外ABCBinary里面有一個placeholder.h,這里解釋一下之前埋下的懸念:因為ABCBinary文件夾里對於源碼的引用沒有copy,所以在提交到git時會自動將文件夾清空(也就是說在git目錄里找不到),因此需要加一個占位防止文件夾不上傳到git,但是切記不要編譯到靜態庫里!

好的,至此一鍵打包腳本也准備好了,通過查看腳本我們發現這個二進制包最終會輸出到根目錄下的./Pod/Products/目錄中,那不還是得傳到git嗎?別急,你忘了gitignore了嗎?

配置.gitignore忽略Pod/文件不就行了嘛,在.gitignore最下面增加忽略

Pod/
復制代碼

好了至此,我們完成了自動打包腳本及git忽略二進制包,再也不用擔心我們的git倉庫空間壓力了(運維小哥哥們表示“尼瑪松了一口氣”)

5、如何在源碼與二進制間切換

在提升編譯速度的前提下,還需要考慮到能隨時進行源碼調試,這就涉及到了如何在源碼與二進制間切換的問題,網上的思路有很多:環境變量、白名單、tag切換等。

這幾種方式在前言部分我們已經講過了,接下來我們介紹一下“環境變量”和“tag切換”這兩種方式:

5.1、 如何利用tag進行切換:

首先我們需要約定好規則:當version中包含.Binary關鍵字時執行prepare_command命令並輸出source為靜態庫,具體操作如下(podspec是用ruby寫的,支持條件判斷):

if s.version.to_s.include?'Binary'

    puts '-------------------------------------------------------------------'
    puts 'Notice:ABC is binary now'
    puts '-------------------------------------------------------------------'
    s.prepare_command = '/bin/bash build_lib.sh'
    s.source_files = 'Pod/Products/include/**'
    s.ios.vendored_libraries = 'Pod/Products/lib/*.a'
    s.public_header_files = 'Pod/Products/include/*.h'    
else
    s.source_files = 'ABC/Classes/**/*'
end
復制代碼

由於tag是根據version走的(tag => s.version.to_s),因此只需要我們修改s.version = '0.1.0.Binary'即可實現二進制打包

好,我們貼一段此時ABC.podspec完整的代碼:

Pod::Spec.new do |s|
  s.name             = 'ABC'
  s.version          = '0.1.0.Binary'
  s.summary          = 'A short description of ABC.'

  s.description      = <<-DESC
TODO: Add long description of the pod here.
                       DESC

  s.homepage         = 'https://github.com/609223770@qq.com/ABC'
  # s.screenshots     = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { '609223770@qq.com' => '609223770@qq.com' }
  s.source           = { :git => 'https://github.com/609223770@qq.com/ABC.git', :tag => s.version.to_s }
  # s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'

  s.ios.deployment_target = '8.0'

  if s.version.to_s.include?'Binary'    
    puts '-------------------------------------------------------------------'
    puts 'Notice:ABC is binary now'
    puts '-------------------------------------------------------------------'
    s.prepare_command = '/bin/bash build_lib.sh'
    s.source_files = 'Pod/Products/include/**'
    s.ios.vendored_libraries = 'Pod/Products/lib/*.a'
    s.public_header_files = 'Pod/Products/include/*.h'    
  else
    puts '-------------------------------------------------------------------'
    puts 'Notice:ABC is source code now'
    puts '-------------------------------------------------------------------'
    s.source_files = 'ABC/Classes/**/*'
  end
end
復制代碼

讓我們來看看效果,在Example下執行pod install,發現切換過來了,Nice 😝~

[圖片上傳中...(image-84413-1594108050322-0)]

接下來驗證本地podspec(若有問題按照提示更改,ssh://xxx.git是你私有源的地址):

pod lib lint --sources=ssh://xxx.git --allow-warnings --verbose --use-libraries
復制代碼

若沒問題,在ABCgit倉庫打一個0.1.0的版本tag,並上傳ABC.podspec至私有源,上傳成功后修改podspec.version0.1.0.Binary再次執行上傳:

pod repo push XXXSpecs ABC.podspec --allow-warnings --verbose --use-libraries
復制代碼

✅ 如果一切順利,我們已經將Binary和源碼的ABC上傳到了私有源。

接下來我們在實際項目實驗一下,Podfile中指定,並執行安裝

pod 'ABC', '~> 0.1.0' # source code

pod install
復制代碼

不出意外源碼ABC安裝成功,這時我們修改tag版本后面加.Binary,再次執行pod install,如下所示:

pod 'ABC', '~> 0.1.0.Binary' # source code

pod install
復制代碼

很遺憾,你可能會發現源碼並沒有切換成功,為什么呢?

原來Pod的版本管理是放在Podfile.lock中,每次執行pod install時若Podfile.lock中已經存在此庫,則只下載Podfile.lock文件中指定的版本進行安裝,否則去搜索這個pod庫在Podfile文件中指定的版本來安裝。

因此,解決辦法有兩種,一種是從Podfile.lock中將包含ABC的地方全部刪除或是干脆直接刪除Podfile.lock,再次執行pod install會發現切換變過來了。

還有一種方法是執行pod update,這也是 update 和 install 的區別,update會讀取Podfile中的版本去更新Podfile.lock文件。(戳我查看pod install和pod update區別

pod update ABC
復制代碼

執行后,先是會更新一下master和其他私有源,再去更新ABC,發現此時切換成功。(缺點就是如果Podfile中如果某些庫沒有指定版本就會更新到最新版本)

5.2、如何利用Ruby環境變量進行切換:

Ruby語法支持一些環境變量的讀取,因此可以在pod install時增加參數以此判斷是否要切換源碼:

IS_BINARY=1 pod install # 1 代表二進制
IS_BINARY=0 pod install # 0 代表源碼
pod install # 默認也是0 源碼
復制代碼

podspec中做修改:

Pod::Spec.new do |s|
  s.name             = 'ABC'
  s.version          = '0.1.0.Binary'
  s.summary          = 'A short description of ABC.'

  s.description      = <<-DESC
TODO: Add long description of the pod here.
                       DESC

  s.homepage         = 'https://github.com/609223770@qq.com/ABC'
  # s.screenshots     = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
  s.license          = { :type => 'MIT', :file => 'LICENSE' }
  s.author           = { '609223770@qq.com' => '609223770@qq.com' }
  s.source           = { :git => 'https://github.com/609223770@qq.com/ABC.git', :tag => s.version.to_s }
  # s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'

  s.ios.deployment_target = '8.0'

  if s.version.to_s.include?'Binary' or ENV['IS_BINARY']    
    puts '-------------------------------------------------------------------'
    puts 'Notice:ABC is binary now'
    puts '-------------------------------------------------------------------'
    s.prepare_command = '/bin/bash build_lib.sh'
    s.source_files = 'Pod/Products/include/**'
    s.ios.vendored_libraries = 'Pod/Products/lib/*.a'
    s.public_header_files = 'Pod/Products/include/*.h'    
  else    
    puts '-------------------------------------------------------------------'
    puts 'Notice:ABC is source code now'
    puts '-------------------------------------------------------------------'
    s.source_files = 'ABC/Classes/**/*'  
  end
end
復制代碼

同tag切換一樣,這種方式在實際項目中切換也存在問題,需要兩個必要步驟:

pod cache clean ABC # 先清理ABC的pod緩存
rm Pods/ABC # 再把ABC從實際項目中的Pods目錄下移除
復制代碼

6、對比兩種方式

方式 優點 缺點
Ruby環境變量切換 1、不需要上傳兩份podspec
2、切換時不需要修改Podfile 1、需要清除私有庫的緩存

2、需要手動刪除/Pods/XXX
3、不能針對單獨庫進行切換,除非自定義白名單之類的規則 |
| tag切換 | 1、可以針對單獨某個庫進行切換 | 1、需要執行pod update(需等待repo master源的更新)
2、私有庫的tag需要打兩個,podspec上傳時需要傳兩次
3、切換時需要手動修改Podfile文件的版本信息 |

7、總結

好,至此iOS組件二進制方案就介紹完了,我們通過ABC項目的實踐了解了整個過程:

  • 創建pod私有庫
  • 在私有庫Demo中創建靜態庫target,並配置頭文件及最低iOS版本支持
  • 創建打包腳本
  • 設置.gitignore忽略輸出的二進制包
  • 配置podspec根據tag版本判斷或根據環境變量判斷
  • 驗證並上傳源碼及二進制的podspec
  • 在實際項目中切換時需要執行pod update或刪除Podfile.lock中相關庫信息

8、鏈接

本文demo相關鏈接如下,另附自動上傳podspec腳本地址(相關文章),喜歡的朋友點個star

作為一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:413038000,不管你是大牛還是小白都歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 大家一起交流學習成長!

推薦閱讀

iOS開發——最新 BAT面試題合集(持續更新中)

作者:被帥醒的吳寶寶
鏈接:https://juejin.im/post/5efaf0655188252e42157e8a


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM