iOS APP 專案模組化經驗分享 — 使用 Private Pod 提升 10倍 code build 效率

Andy Chang
Tomofun Tech Blog
Published in
14 min readJan 17, 2024
ios 專案模組化經驗 (CocoaPods應用心得)

隨著公司產品與功能推陳出新,專案的複雜度也跟著不斷提高,為了提升開發與維護的效率以及確保 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 拆分模組

根據上面的原則,這次需要拆分出兩個模組:

  1. 全新的日曆功能 → NewCalendar
  2. 給日曆使用的泛用功能 → 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 的時間縮短了!

比較主專案與 NewCalendar 的範例專案在 w/ & w/o Cache data 的情況下 Build time 的差異

專案切分成較小的 Pods 後,開發或維護 Pod 的時候需要編譯的檔案也跟著變小,加快了 Code Build 的速度!除此之外,模組化的程式碼還有很多好處:

專案擴展彈性高,每個 Module 都可以獨立進行開發和測試

簡化 Test case,針對每個獨立的小 Module,可以相對輕鬆且完整地完成測試

提高 Debugging 的效率,可以快速將範圍縮小至某幾個 Modules 做驗證

隨著公司產品不斷創新完善,日益膨脹的專案是大家必需面對的課題,準備好具備彈性良好的開發架構,再產品進化的同時也維持良好的品質!

--

--