iOS APP 專案模組化經驗分享 — 使用 Private Pod 提升 10倍 code build 效率
隨著公司產品與功能推陳出新,專案的複雜度也跟著不斷提高,為了提升開發與維護的效率以及確保 App 的品質,iOS Team 開始著手把全新以及既有的功能模組化,並透過 Cocoapods 打包成一個一個 Private Pod 來管理調用。接著就馬上來分享我們實作的過程吧!
先來說說龐大專案在開發與維護上帶來的影響吧!
- 邏輯判斷複雜的畫面需要增加新功能時,儘管小心翼翼還是難免會有 Bug
- 程式碼耦合度高,容易發生改了 A 但 B 卻壞掉的情況
- 編譯器 Code Build 的時間也跟著專案一起拉長,每天不下百次的 Code Build 所累積的時間非常可觀
專案裡的每個 Feature 都無比重要,該如何面對日益龐大的專案呢?
❌ 重構冗長的程式碼 → 改善幅度有限且需要承擔重構產生 bug 的風險同時也增加 QA 的 effort
✅ 模組化 → 透過把大專案拆成數個小專案,達到立竿見影的效果
馬上來分享實例吧!
這次要開發全新的日曆功能,並利用模組化的概念來實作!
利用 Cocoapods (Dependency Manager) 管理模組,在瘦身專案的同時提升專案擴展的可能性。
【模組化原則(w/ Cocoapods)】
- 泛用的功能可以抽出來寫成 Foundation 的模組 i.e., API manager, custom extensions…etc.
- 依照 Feature 拆分模組
根據上面的原則,這次需要拆分出兩個模組:
- 全新的日曆功能 → NewCalendar
- 給日曆使用的泛用功能 → CoreComponent
【工具說明】
- 什麼是 Cocoapods?
CocoaPods 是一個提供第三方 Library 的平台,支援 Swift 和 Objective-C 語言。透過它可以輕鬆地整合和管理第三方 Library。開發者通過在 Podfile 中指定 Library 和版本後執行 pod install 來進行整合使用
- 接著簡單說明一下執行 pod install 的背後做了什麼呢?
執行 pod install 後,Cocoapods 會從專案目錄底下的 Podfile 裡找到需要安裝的 Libraries(Pods),並向 Spec repo 要求 pod 對應的 podspec,podspec 裡會描述 Remote Repository 的位置,Cocoapods 會從 Repository 下載 Source code 後整合進 Xcode 專案。
- 什麼是 Cocoapods Spec Repository?
從上面的流程發現,podspec 扮演著很重要的角色,Repo 位置、版本或其他 Library 的資訊都記錄在裡面。Spec Repository 則是 Cocoapods 用來存放他們數以萬計 podspec 的地方,讓開發者可以透過這個 public repo 來取得需要的 Library。
→ 使用 Private Pod 時不需要把 podspec 丟到公共的 Spec Repository,需要自己再另外開 Private Spec Repository
知道 Cocoapods 是如何運作之後,馬上來說明步驟吧!
- 馬上來準備需要的材料吧
✔︎ 初始化專案(包含 podspec, example project)
✔︎ 建立 Remote Repository (Github / Bitbucket…)
✔︎ 功能開發
✔︎ 新增 Git Tag 管理版本 - Pod Configuration
✔︎ Pod repo add
✔︎ 設定 & 驗證 podspec
✔︎ pod repo push - 一切就緒!與 Main Project 整合
✔︎ Podfile 新增 Pod Spec Repo 的 Source
✔︎ Podfile 新增使用的 pod
✔︎ pod install
【步驟說明】
Step 1. 初始化專案
# 產生包含 Example project 和 podspec file 的專案
# run following command to initiate project
# CoreComponent should be replaced
$ pod lib create CoreComponent
# Also create project for NewCalendar
$ pod lib create NewCalendar
到 Example 執行 pod install 後,看到 Development Pods 的目錄,就可以開始開發了
# Move to directory of Example projects
$ cd CoreComponent/Example
$ pod install
# NewCalendar
$ cd NewCalendar/Example
$ pod install
Step 2. 建立 Remote Repository (Github / Bitbucket…)
Private Pod 不會把 podspec 推到 Cocoapods Public 的 Spec 上,除了 CoreComponent 和 NewCalendar 以外還需要再建一個 Private Spec Repo 來放 podspec
- CoreComponent
- NewCalendar
- PrivateSpec
Step 3. 功能開發
Step 4. 新增 Git Tag 管理版本
# Add tag
$ git tag '0.1.0'
# Push to remote
$ git push --tags
Step 5. pod repo add
把 Step2 建好的 Repository 加進 local 端的 Cocoapods
$ pod repo add CoreComponent git@bitbucket.org:test/CoreComponent.git
$ pod repo add NewCalendar git@bitbucket.org:test/NewCalendar.git
$ pod repo add PrivateSpec git@bitbucket.org:test/PrivateSpec.git
執行完成後,可以在 ~/.cocoapods/repos 目錄底下或是執行 pod repo list 確認成功加入 🙌
Step 6. 設定 & 驗證 podspec
- 設定 podspec
✔︎ 可以從 Step1 做出來的專案目錄底下找到 podspec file
#
# Be sure to run `pod lib lint NewCalendar.podspec' to ensure this is a
# valid spec before submitting.
#
# Any lines starting with a # are optional, but their use is encouraged
# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
#
Pod::Spec.new do |s|
s.name = 'NewCalendar'
s.version = '0.1.0'
s.summary = 'A short description of NewCalendar.'
# This description is used to generate tags and improve search results.
# * Think: What does it do? Why did you write it? What is the focus?
# * Try to keep it short, snappy and to the point.
# * Write the description between the DESC delimiters below.
# * Finally, don't worry about the indent, CocoaPods strips it!
s.description = <<-DESC
TODO: Add long description of the pod here.
DESC
s.homepage = 'https://bitbucket.org/test/NewCalendar/src/master'
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'Andy Chang' => 'andy_chang@tomofun.com' }
s.source = { :git => 'git@bitbucket.org:test/NewCalendar.git', :tag => s.version.to_s }
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
s.ios.deployment_target = '10.0'
s.source_files = 'NewCalendar/Classes/**/*'
# s.resource_bundles = {
# 'NewCalendar' => ['NewCalendar/Assets/**/*']
# }
# s.public_header_files = 'Pod/Classes/**/*.h'
# s.frameworks = 'UIKit', 'MapKit'
s.dependency 'CoreComponent'
end
✔︎ 可以從 Step1 做出來的專案目錄底下找到 podspec file
s.resource_bundles = {
'NewCalendar' => ['NewCalendar/Assets/**/*']
}
✔︎ 使用 Assets 時,需要 specify NewCalendar 的 bundle
// Usage
// Get specific bundle of NewCalendar
extension Bundle {
static var newCalendarBundle: Bundle? {
return Bundle(for: {{AnyPodClass}}.self)
}
}
// SwiftUI
Image(
_ name: String,
bundle: Bundle? = nil
)
// use bundle from NewCalendar
Image("assetName", bundle: .newCalendarBundle)\
// w/o given specific bundle, main bundle will set as default
Image("assetName")
// UIKit
UIImage(
named name: String,
in bundle: Bundle?,
with configuration: UIImage.Configuration?
)
// use bundle from NewCalendar
UIImage(named: "assetName", in: .newCalendarBundle, with: nil)
// w/o given specific bundle, main bundle will set as default
UIImage(named: "assetName")
✔︎ 同樣地 NewCalendar 會需要使用 CoreComponent 的功能,podspec 也要新增對 dependency 的描述
s.dependency 'CoreComponent'
# Add multiple dependencies
s.dependency 'Dependency1'
s.dependency 'Dependency2'
s.dependency 'Dependency3'
✔︎ 其他 Podspec Configuration 可以參考 Podspec Syntax Reference
- 驗證 podspec
因為 NewCalendar 的 podspec 有描述和 CoreComponent 的 dependency,驗證時透過 — source 表明 PrivateSpec 的位置來找到 CoreComponent 的 Repository。
$ pod lib lint CoreComponent.podspec --allow-warnings
# Pod with dependency, should add --source for indicating private pod specs
$ pod lib lint NewCalendar.podspec --allow-warnings --sources=git@bitbucket.org:test/PrivateSpecs.git
Step 7. pod repo push
Almost there! 驗證確認沒問題後,就可以把 podspec 推上 PrivateSpec 了
$ pod repo push PrivateSpec CoreComponent.podspec --allow-warnings
# Pod with dependency, should add --source for indicating private pod specs
$ pod repo push PrivateSpec NewCalendar.podspec --allow-warnings --sources=git@bitbucket.org:test/PrivateSpec.git
到這邊 Pod 已經都準備好了,接著馬上把 CoreComponent 和 NewCalendar 的功能 import 到主專案上吧!
Step 8. Podfile 新增 Pod Spec Repo 的 Source
# indicate PrivateSpec for 'pod install' usage
source 'git@bitbucket.org:tomofun/PrivateSpec.git'
Step 9. Podfile 新增使用的 pod
# Pods for MainProject
pod 'CoreComponent'
pod 'NewCalendar'
Step 10. pod install
$ pod install
看看成果吧,Code build 的時間縮短了!
專案切分成較小的 Pods 後,開發或維護 Pod 的時候需要編譯的檔案也跟著變小,加快了 Code Build 的速度!除此之外,模組化的程式碼還有很多好處:
專案擴展彈性高,每個 Module 都可以獨立進行開發和測試
簡化 Test case,針對每個獨立的小 Module,可以相對輕鬆且完整地完成測試
提高 Debugging 的效率,可以快速將範圍縮小至某幾個 Modules 做驗證
隨著公司產品不斷創新完善,日益膨脹的專案是大家必需面對的課題,準備好具備彈性良好的開發架構,再產品進化的同時也維持良好的品質!