所有文章已搬遷到個(gè)人站點(diǎn):me.harley-xk.studio,歡迎訪問(wèn)留言
原由
最近跟著公司大佬在做 Laravel 后端開(kāi)發(fā),要求使用 php lint 進(jìn)行代碼規(guī)范檢查之后才能 push 代碼,保證所有人寫(xiě)出風(fēng)格統(tǒng)一的代碼,方便后期的維護(hù)和 Review,于是開(kāi)始往老本行上反思。
想想自己寫(xiě)了五六年的 iOS ,雖然自認(rèn)代碼還是寫(xiě)的很規(guī)整的,但是寫(xiě) high 了之后還是會(huì)忽略很多細(xì)節(jié)上的東西,雖說(shuō)無(wú)傷大雅,但是軟件開(kāi)發(fā)作為一門工程性質(zhì)的東東,始終覺(jué)得規(guī)范化是一件很重要的事情。
在之前的公司也曾經(jīng)在 iOS 組內(nèi)部推行過(guò)代碼規(guī)范的實(shí)施,但那時(shí)候還只是停留在弄個(gè) Word 文檔,把各條規(guī)范列一列,然后開(kāi)個(gè)小會(huì)普及下的程度上?,F(xiàn)在接觸了不少其他開(kāi)發(fā)領(lǐng)域的東西,越來(lái)越覺(jué)得對(duì)于開(kāi)發(fā)者來(lái)說(shuō)提高視角去了解各個(gè)方面是多么重要的一件事情。不同領(lǐng)域的經(jīng)驗(yàn)、做事的方式、思路,都可以相互借鑒與融合。
于是開(kāi)始尋找在 iOS 下實(shí)行類似方案的可能性。說(shuō)來(lái)也巧,最近在看 iOS 相關(guān)資料的時(shí)候發(fā)現(xiàn)了 SwiftLint 這玩意兒,遂打算來(lái)實(shí)踐下。
SwiftLint 是啥?
SwiftLint 是 Realm 推出的一款 Swift 代碼規(guī)范檢查工具,Realm 就不用介紹了,他們家推出的移動(dòng)端跨平臺(tái)數(shù)據(jù)庫(kù)在業(yè)內(nèi)的名氣還是很大的,就算沒(méi)有用過(guò),相信大多數(shù)人也是聽(tīng)過(guò)的。
SwiftLint 基于 Github 公布的 Swift 代碼規(guī)范進(jìn)行代碼檢查,并且能夠很好的和 Xcode 整合。配置好所有的設(shè)置之后,在 Xcode 中執(zhí)行編譯時(shí),SwiftLint 會(huì)自動(dòng)運(yùn)行檢查,不符合規(guī)范的代碼會(huì)通過(guò)警告或者 error 的形式指示出來(lái),并且擁有豐富的配置項(xiàng),可以進(jìn)行大量的自定義,相當(dāng)方便。
安裝配置
SwiftLint 有多種不同的安裝方式,可以根據(jù)自己的喜好選擇。
使用 Homebrew 安裝
Homebrew 是 macOS 自帶的包管理工具,使用這種方式安裝也是最簡(jiǎn)單的:
brew install swiftlint
使用 CocoaPods 安裝
通過(guò) CocoaPods 安裝同樣很簡(jiǎn)單,只需要在 Podfile 中添加依賴:
pod 'SwiftLint'
之后執(zhí)行 pod install 就可以自動(dòng)安裝了,這種方式會(huì)將 SwiftLint 安裝到項(xiàng)目的 Pods/ 目錄下。如果你想要針對(duì)不同的項(xiàng)目使用不同的 SwiftLint 版本,這是一種很好的解決方案(Homebrew 會(huì)自動(dòng)安裝最新版本)。
需要注意的是使用這種方案會(huì)將整個(gè) ** SwiftLint** 以及他的依賴包的完整資源文件都安裝到 Pods/ 目錄中去,所以在使用版本管理工具比如 git 時(shí)要注意設(shè)置忽略相關(guān)目錄。
使用安裝包
SwiftLint 還支持使用 pkg 安裝包進(jìn)行安裝,在官方的 Github 頁(yè)面可以找到最新發(fā)布的安裝包。
編譯源代碼
SwiftLint 完全使用 Swift 開(kāi)發(fā),并且它是基于 MIT License 開(kāi)源的,所以你可以下載它的源代碼,然后通過(guò)以下命令編譯安裝:
git submodule update --init --recursive; make install
安裝完成
等待安裝完成,輸入 swiftlint help 可以查看所有可用的命令:
? ~ swiftlint help
Available commands:
autocorrect Automatically correct warnings and errors
help Display general or command-specific help
lint Print lint warnings and errors (default command)
rules Display the list of rules and their identifiers
version Display the current version of SwiftLint
到此 SwiftLint 就安裝完成了
配置 Xcode
接下來(lái)需要在工程中配置相關(guān)編譯選項(xiàng),才能使 SwiftLint 在 Xcode 中運(yùn)行起來(lái)。配置也很簡(jiǎn)單,只需要在 Xcode 的 Build Phases 中新建一個(gè) Run Script Phase 配置項(xiàng),在里面添加如下代碼:
if which swiftlint >/dev/null; then
swiftlint
else
echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"
fi
如圖所示:
[圖片上傳失敗...(image-933261-1580636760609)]
如果是通過(guò) CocoaPods 安裝的 SwiftLint 需要將 swiftlint 替換為 CocoaPods 中的路徑: "${PODS_ROOT}/SwiftLint/swiftlint"。
這里其實(shí)是設(shè)置了一個(gè)自動(dòng)編譯腳本,每次運(yùn)行編譯都會(huì)自動(dòng)執(zhí)行這個(gè)腳本,如果正確安裝了 SwiftLint,就會(huì)執(zhí)行 SwiftLint 中的代碼規(guī)范檢查,如果沒(méi)有安裝,腳本會(huì)拋出一個(gè)沒(méi)有安裝 SwiftLint 并提示下載的警告,方便提醒團(tuán)隊(duì)團(tuán)隊(duì)中沒(méi)有安裝的成員。
當(dāng)然,你也可以設(shè)置為強(qiáng)制要求安裝,這時(shí)如果沒(méi)有安裝則無(wú)法通過(guò)編譯。只需要在腳本中 echo "warning: ..." 之后添加一行代碼:exit 1,這樣一來(lái),如果沒(méi)有安裝 SwiftLint,編譯時(shí)會(huì)直接拋出一個(gè)編譯錯(cuò)誤而非警告,提示需要安裝 SwiftLint。
到此配置就完成了,是不是很簡(jiǎn)單。
自定義配置
現(xiàn)在編譯一下項(xiàng)目看看,是不是很可怕??:

不要被 999+ 嚇到了,仔細(xì)看一下具體的錯(cuò)誤,會(huì)發(fā)現(xiàn)好多都是第三方庫(kù)的代碼規(guī)范問(wèn)題,而且好多問(wèn)題的級(jí)別被設(shè)置成為了 error
這樣子可不行,第三方庫(kù)的代碼規(guī)范問(wèn)題不能讓我們自己的項(xiàng)目來(lái)背鍋,接下來(lái)需要做一些配置,讓 SwiftLint 在做代碼規(guī)范檢查的時(shí)候自動(dòng)忽略 CocoaPods、Carthage 等包管理器引入的第三方庫(kù)(當(dāng)然,手動(dòng)導(dǎo)入的第三方庫(kù)也能設(shè)置忽略)
首先需要在項(xiàng)目的根目錄下新建一個(gè)名為 .swiftlint.yml 的配置文件,輸入如下內(nèi)容:
excluded:
- Pods
excluded 配置項(xiàng)用來(lái)設(shè)置忽略代碼規(guī)范檢查的路徑,可以指定整個(gè)文件夾,也可以指定精確路徑下的文件,通過(guò) - xxxx 的形式列在下面就可以了,比如如果你的項(xiàng)目使用 Carthage 管理第三方庫(kù)的話,可以將 Carthage 目錄添加到忽略列表:
excluded:
- Pods
- Carthage
保存之后再來(lái)編譯下,少了很多編譯錯(cuò)誤了,至少第三方庫(kù)的編譯錯(cuò)誤都被干掉了,oh yeah~

不過(guò)錯(cuò)誤和警告依然很多,繼續(xù)往下看,發(fā)現(xiàn)大量的
trailing_whitespace 的警告,這個(gè)是之前寫(xiě)代碼不注意留下的,在空行中包含了空格,雖然肉眼看不出來(lái),但是 SwiftLint 火眼金睛啊。
你可以選擇更正所有這些不規(guī)范的問(wèn)題,不過(guò)如果這個(gè)這個(gè)項(xiàng)目是遺留下來(lái)的老項(xiàng)目,可能存在大量類似的問(wèn)題,手動(dòng)更正這些問(wèn)題需要相當(dāng)多的精力,這時(shí)候可以選擇開(kāi)啟忽略這類問(wèn)題,只需要在
.swiftlint.yml 文件中加入如下代碼:
disabled_rules:
- trailing_whitespace
再次編譯,發(fā)現(xiàn) trailing_whitespace 的問(wèn)題已經(jīng)不再提示了,你可以用同樣的方法配置忽略特定的規(guī)則。
其他規(guī)則
Xcode自動(dòng)生成的代碼經(jīng)常包含大段的注視,我們經(jīng)常會(huì)選擇保留這些注釋。不過(guò) SwiftLint 有一個(gè) line_length 的規(guī)則,默認(rèn)是會(huì)檢查注釋的長(zhǎng)度的,可以在 .swiftlint.yml 中設(shè)置忽略檢查注釋的長(zhǎng)度:
line_length:
warning: 110
ignores_function_declarations: true
ignores_comments: true
這段代碼設(shè)置了 line_length 的檢查規(guī)則:
warning: 110 表示單行字符數(shù)超過(guò) 110 時(shí)拋出警告,你也可以設(shè)置為其他的值。
ignores_function_declarations 表示是否忽略檢查函數(shù)定義的長(zhǎng)度
ignores_comments 設(shè)置是否忽略檢查注釋的長(zhǎng)度
當(dāng)然,你也可以在 disabled_rules 中設(shè)置忽略單行長(zhǎng)度規(guī)則
這里有所有目前已經(jīng)實(shí)現(xiàn)了的規(guī)則。你也可以實(shí)現(xiàn)自己的規(guī)則,然后給他們發(fā) Pull requests。
本文的最后附上了我自己的 .swiftlint.yml 文件,你可以在 SwiftLint 的官方文檔找到更多關(guān)于自定義規(guī)則的說(shuō)明。
設(shè)置完所有配置之后,再次編譯代碼,之后就可以根據(jù)錯(cuò)誤提示去更正不規(guī)范的代碼了。
.swiftlint.yml 的嵌套
.swiftlint.yml 配置文件支持嵌套,因此
- 你可以給每個(gè)文件夾下的代碼單獨(dú)指定不同的規(guī)則設(shè)置
- 每個(gè)文件會(huì)匹配距離自己層級(jí)最近的父文件夾中的配置文件
- 嵌套的配置文件中的
excluded和included配置會(huì)被忽略
結(jié)語(yǔ)
終于寫(xiě)完了第一篇技術(shù)博客,深感寫(xiě)文章的不易,希望能保持下去。
這篇文章是我在初步研究了 SwiftLint 之后寫(xiě)的,一定有很多謬誤和不足之處,各位輕噴- -
<a name="SwiftLint">.swiftlint.yml</a>
disabled_rules: # rule identifiers to exclude from running
- force_cast
- trailing_whitespace
- cyclomatic_complexity
- unused_closure_parameter
# - colon
# - comma
# - control_statement
# opt_in_rules: # some rules are only opt-in
# - empty_count
# - missing_docs
# # Find all the available rules by running:
# # swiftlint rules
# included: # paths to include during linting. `--path` is ignored if present.
# - Docs.M/*/*.swift
excluded: # paths to ignore during linting. Takes precedence over `included`.
- Carthage
- Pods
# - Source/ExcludedFolder
# - Source/ExcludedFile.swift
# configurable rules can be customized from this configuration file
# binary rules can set their severity level
# force_cast: warning # implicitly
force_try:
severity: warning # explicitly
# rules that have both warning and error levels, can set just the warning level
# implicitly
line_length:
warning: 200
ignores_function_declarations: true
ignores_comments: true
# they can set both implicitly with an array
type_body_length:
- 300 # warning
- 400 # error
# or they can set both explicitly
file_length:
warning: 500
error: 1200
# naming rules can set warnings/errors for min_length and max_length
# additionally they can set excluded names
# type_name:
# min_length: 4 # only warning
# max_length: # warning and error
# warning: 40
# error: 50
# excluded: iPhone # excluded via string
identifier_name:
min_length: # only min_length
error: 3 # only error
excluded: # excluded via string array
- id
# - URL
# - GlobalAPIKey
reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji)