今是昨非

今是昨非

日出江花红胜火,春来江水绿如蓝

iOS 編譯時間優化

背景#

目前 App 專案不大,但是清空後重新編譯時間需要 200 多秒,感覺不太合理,所以,就著手排查了一下。

通常的編譯時間優化都是分為三個部分

  • Xcode 編譯設定的優化
  • 代碼或函數編譯時間的優化
  • 三方庫編譯時間的優化

這裡就根據上面三個部分來一一排查。

實現#

Xcode 編譯設定的優化#

使用的是 Xcode 13.4,網上搜到的,關於 Xcode 設定New Build System,及Build Settings中設定Debug Information Format的都不需要設定了,默認已經是合理的。至於Optimization Level的設定,設定後,雖然編譯速度可以提升,但是對於 Debug 不友好,所以,這裡也不做設定。故而針對這項優化什麼都沒有處理。

代碼或函數編譯時間的優化#

這方面主要是針對 Swift,首先把編譯耗時的方法顯示出來,在Build SettingsOther Swift Flags添加如下設定,意思是顯示編譯超過 200ms 的函數或者類型檢查超過 300ms 的函數顯示 warning,這裡 200ms 是自己設定,可針對專案的真實情況設定:


-Xfrontend -warn-long-function-bodies=200
-Xfrontend -warn-long-expression-type-checking=200

筆者這裡修改的內容有下面這幾種:

待優化代碼示例 1:


let count:Int = Int((self?.listParamItem.pageSize ?? 0) * ((self?.needRefreshPageNum ?? 0) - 1))
let endIndex = count + Int(self?.listParamItem.pageSize ?? 0) - 1
if (self?.dataList.count ?? 0) > endIndex {

上面的代碼中,可空和??混用然後類型轉換,再去進行比較,代碼雖然沒錯,但是編譯真的耗時,這些方法超出了 500ms。針對這些修改為如下代碼,編譯時間變為 100ms 以內,減少了 5 倍:


if let self = self {
  let count: Int = self.listParamItem.pageSize * (self.needRefreshPageNum - 1)
  let endIndex: Int = count + self.listParamItem.pageSize - 1
  if self.dataList.count > endIndex {
}

待優化代碼示例 2:


let dic = [
    "aaa": xxx ?? yyy, 
    "bbb": ["ccc": "xxx", "eee": 5],
    "ddd": 5
]

上面代碼中,看起來沒什麼問題,但是編譯時間超過了 200ms,可能是類型推斷導致。修改為如下代碼後,編譯耗時不再超過 200ms:


var dic1: [String: Any] = [:]
dic1["ccc"] = "xxx"
dic1["eee"] = 5

var dic2: [String: Any] = [:]
dic2["aaa"] = xxx
dic2["bbb"] = dic1
dic2["ddd"] = 5

待優化代碼示例 3


if type == .aaa ||
type == .bbb ||
type == .ccc ||
type == .ddd ||
type == .eee ||
type == .xxx {
  doSomething()
} else {
    doAnotherThing()
}

上面的代碼除了不優雅外,編譯還耗時,修改為Switch case如下:


switch type {
case .aaa,
.bbb,
.ccc,
.ddd,
.eee,
.xxx: 
  doSomething()
default:
    doAnotherThing()
}

待優化代碼示例 4


let fontAdd: CGFloat = 14.0

protocolBtn.snp.makeConstraints { make in
    make.left.equalTo(agreeLabel.snp.right).offset(1)
    make.centerY.equalTo(checkBtn.snp.centerY)
    make.width.equalTo(kTransitionW(150 + fontAdd * 10))
}

上面的代碼中小數和整數混合運算,讓 Swift 來推斷需要的是小數還是整數。修改為如下後,編譯超時警告消失:


let fontAdd: CGFloat = 14.0
let width: CGFloat = 150.0 + fontAdd * 10.0

protocolBtn.snp.makeConstraints { make in
    make.left.equalTo(agreeLabel.snp.right).offset(1.0)
    make.centerY.equalTo(checkBtn.snp.centerY)
    make.width.equalTo(kTransitionW(width))
}

最後還有一類是某個方法特別長的,也出現了編譯超時警告,拆分為多個子方法解決。

三方編譯庫的優化#

專案中集成的三方庫都是 CocoaPods 的方式集成,所以每次 clean build 後,三方庫都會重新編譯。設定 Xcode 顯示編譯時間,打開 terminal,複製下面,去運行,重啟 Xcode。


$ defaults write com.apple.dt.Xcode ShowBuildOperationDuration -bool YES

然後 clean,重新 build,可以看到詳細的編譯時間如下:

編譯時間

這裡的ZLPhotoBrowser就是通過 Pod 方式安裝的第三方庫,可以看到這個庫的源文件編譯時間用了 30 秒。。。這個頁面可以詳細看到每個庫的編譯時間和專案的編譯時間,針對編譯時間長的三方庫,改為Carthage方式導入,可參考Carthage 的使用Carthage是將要使用的第三方庫,下載並編譯為xcframework,然後把生成的xcframework導入到工程中使用,故而每次 clean build 後,並不會重新編譯,從而可以節省編譯時間。

總結#

編譯時間優化的三個部分總結如下:

  • Xcode 編譯設定的優化 —— 新版本 Xcode 無需設定
  • 代碼或函數編譯時間的優化 —— 需要注意類型推斷和複雜計算的優化以及運算符的使用,但是並不是所有的代碼編譯超時都需要進行優化,在編譯優化和代碼的簡潔、優雅、Swift 特性之間,要自己選擇平衡點。
  • 三方庫編譯時間的優化 —— 這個優化推薦使用,針對不會更改的三方庫,改為Carthage方式導入,可以減少重新編譯時間。

建議大家優化前,首先分析專案的編譯時間卡在哪裡,分析是三方庫的編譯時間太長還是專案源文件的編譯時間太長,從而決定著重進行步驟二還是步驟三。

參考#

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。