Swift開發規范
此文檔與Apple官方Swift代碼規范文檔不沖突,只是在官方文檔的基礎上增加了的部分規范。
-
命名規范
-
編碼風格
-
語法規范
-
框架使用
-
網絡請求
命名規范
- 類名
采用駝峰命名法,首字母大寫, 示例:HomeViewController
- 結構體名
采用駝峰命名法,首字母大寫,示例:Coupon
- 變量名
采用駝峰命名法,首字母小寫,示例:pageNumber
- 枚舉名
采用駝峰命名法,首字母大寫,示例:OrderStatus
- 枚舉值名
采用駝峰命名法,首字母小寫。一般情況下一個單詞就可以。示例:
enum OrderStatus: Int {
case normal
case expired
case paid
}
- 全局常量名
采用駝峰命名法,首字母大寫,并用小寫的項目縮寫名為前綴。如“微信”項目名的小寫縮寫為“wx”, 示例:
let weAnimationDuration: TimeInterval = 0.25
- 全局函數名
采用駝峰命名法,首字母小寫,并用小寫的項目縮寫名+下劃線為前綴。如“微信”項目名的小寫縮寫為“wx”, 前綴即“wx_”,示例:
func we_checkLogin() -> Bool {
...
}
- 協議名
- 如果是單純的協議,則采用駝峰命名法,首字母大寫,后綴為"protocol"。示例:
SomeProtocol
- 如果是用來做代理的,則采用駝峰命名法,首字母大寫,后綴為"delegate"。示例:
OrderCellDelegate
- 協議方法名
采用駝峰命名法,首字母小寫。在創建一個代理方法時,第一個未命名的參數應該是代理源。(UIKit中有很多這樣的例子),示例:
// 推薦:
func namePickerView(_ namePickerView: NamePickerView, didSelectName name: String)
func namePickerViewShouldReload(_ namePickerView: NamePickerView) -> Bool
// 不推薦
func didSelectName(namePicker: NamePickerViewController, name: String)
func namePickerShouldReload() -> Bool
- 可選協議方法名
使用@objc
+optional
關鍵字,示例:
@objc protocol OrderCellDelegate: class {
// 除了自身對象之外,還有操作的控件作為參數
func orderCell(cell: weOrderCell, didClick checkButton: UIButton)
// 只有自身對象作為參數
@objc optional func orderCellDidClickCheckButton(cell: weOrderCell)
}
- 代理變量名
統一使用"delegate",并用weak修飾(如果需要除class之外的類型實現,則可不用)。示例:
weak var delegate: OrderCellDelegate?
- 控制器類名
要求同類名,但要以“ViewController”結尾,示例:OrderViewController
- 視圖類名
要求同類名,但要以“View”結尾,示例:OrderDetailView
- 模型類名
要求同類名,無需特殊后綴,示例:Order
- 控件名
需要在名稱中指明控件的類型,不要使用lbl
或btn
等縮寫,示例:nameLabel
,confirmButton
- 資源文件名
- 圖片資源需要在名稱中加上功能模塊名,防止重復,示例:
img_home_right_arrow
,img_order_locate
- 聲音資源名稱表明用途即可,示例:
qr_sound
- 閉包名
要求同類名,但要以"closure"結尾,示例:OrderViewClosure
- 擴展文件名
文件名后加上項目縮寫,示例:UILabel+Extension(we).swift
- 擴展方法名
采用駝峰命名法,首字母小寫,并用小寫的項目縮寫名+下劃線為前綴。如“微信”項目名的小寫縮寫為“wx”, 前綴即“wx_”,示例:
extension UIView {
/// 移除所有子控件
func we_removeAllSubviews() {
...
}
}
- 首字母縮略詞在命名中一般來說都是全部大寫,例外的情形是如果首字母縮略詞是一個命名的開始部分,而
這個命名需要小寫字母作為開頭,這種情形下首字母縮略詞全部小寫。示例:
// "HTML" 是變量名的開頭, 需要全部小寫 "html"
let htmlBodyContent: String = "<p>Hello, World!</p>"
// 推薦使用 ID 而不是 Id
let profileID: Int = 1
// 推薦使用 URLFinder 而不是 UrlFinder
class URLFinder {
...
}
- 命名應該具有描述性和清晰的。
// 推薦
class RoundAnimatingButton: UIButton { /* ... */ }
// 不推薦
class CustomButton: UIButton { /* ... */ }
- 不要縮寫,簡寫命名,或用單個字母命名。
// 推薦
class RoundAnimatingButton: UIButton {
let animationDuration: NSTimeInterval
func startAnimating() {
let firstSubview = subviews.first
}
}
// 不推薦
class RoundAnimating: UIButton {
let aniDur: NSTimeInterval
func srtAnmating() {
let v = subviews.first
}
}
編碼格式
- 盡可能的多使用
let
,少使用var
。 - 如果變量類型可以依靠推斷得出,不建議聲明變量時指明類型。示例:
var name = "jack"
- 二元運算符(+, ==, 或->)的前后都需要添加空格,左小括號后面和右小括號前面不需要空格。
let myValue = 20 + (30 / 2) * 3
if 1 + 1 == 3 {
fatalError("The universe is broken.")
}
func pancake() -> Pancake {
...
}
- 逗號后面要加一個空格,示例:
var nums = [1, 2, 3, 4]
- 左大括號不用另起一行,并與之前的元素相隔一個空格。
class SomeClass {
func someMethod() {
if x == y {
...
} else {
...
}
}
}
- 注釋的雙斜杠跟注釋內容之間隔一個空格。示例:
// 我是注釋
- 盡量不使用
self.
,除非方法參數名與屬性同名。 - 使用
// MARk: -
按功能為一個文件中的代碼分塊, 下面一行保留為空行。示例:
class Pirate {
// MARK: - 實例屬性
private let pirateName: String
// MARK: - 初始化
init() {
/* ... */
}
}
- 使用擴展來實現協議方法。示例:
extension ViewController: OrderCellDelegate {
func orderCell(cell: OrderCell, didClick checkButton: UIButton) {
...
}
}
- 使用
@available(iOS 10.0, *)
來標明起始系統版本號 - 為同一對象的各屬性賦值時,等號‘=’對齊。示例:
let my = MyClass()
my.name = "張四"
my.age = 10
my.address = "望京綠地中心"
- 盡可能避免使用強制轉換和強制解包。
- 使用4個空格進行縮進。
- 每行最多160個字符,避免一行過長。(Xcode->Preferences->Text Editing->Page guide at column: 設置成160即可)
- 在使用一些語句如
else
,catch
等緊隨代碼塊的關鍵詞的時候,確保代碼塊和關鍵詞在同一行。下面if/else
和do/catch
的例子。示例:
if someBoolean {
// something you want
} else {
// something you don't want
}
do {
let fileContents = try readFile("filename.txt")
} catch {
print(error)
}
- 推薦把訪問修飾符放到第一個位置。
// 推薦
private static let kMyPrivateNumber: Int
// 不推薦
static private let kMyPrivateNumber: Int
- case 語句 應和 switch 語句左對齊,并在 標準的 default 上面。
switch problem {
case .attitude:
print("At least I don't have a hair problem.")
case .hair:
print("Your barber didn't know when to stop.")
case .hunger(let hungerLevel):
print("The hunger level is \(hungerLevel).")
}
- 當在寫一個變量類型,一個字典里的主鍵,一個函數的參數,遵從一個協議,或一個父類,不用在分號前添加空格。
// 指定類型
let pirateViewController: PirateViewController
// 字典語法(注意這里是向左對齊而不是分號對齊)
let ninjaDictionary: [String: AnyObject] = [
"fightLikeDairyFarmer": false,
"disgusting": true
]
// 調用函數
someFunction(someArgument: "Kitten")
// 父類
class PirateViewController: UIViewController {
...
}
// 協議
extension PirateViewController: UITableViewDataSource {
...
}
語法規范
- 使用顯式類型和空集合。類型在賦值操作符的左邊,空實例在賦值操作符的右邊。
錯誤示例:
var x = [String: Int]()
var y = [Double]()
var z = Set<String>()
var mySet = MyOptionSet()
正確示例:
var x: [String: Int] = [:]
var y: [Double] = []
var z: Set<String> = []
var mySet: MyOptionSet = []
- 可選類型拆包時,使用
if let
或guard let
判斷。 - 多個可選類型拆包取值時,將多個
if let
或guard let
判斷合并。示例:
if let name = person.name, let age = person.age {
}
- 盡量不要使用
as!
或try!
,使用if let as?
判斷。示例:
if let name = person.name as? String {
}
- 非全局常量要定義在類的里面,不要定義在類的外面。示例:
class ViewController: UIViewController {
let cellID = "GirlCell"
...
}
- 跨多行函數聲明縮進時,參數名左對齊(不是冒號對齊)。示例:
func myFunctionWithManyParameters(parameterOne: String,
parameterTwo: String,
parameterThree: String) {
print("\(parameterOne) \(parameterTwo) \(parameterThree)"
}
- 多if語句時的縮進,看示例:
if myFirstVariable > (mySecondVariable + myThirdVariable)
&& myFourthVariable == .SomeEnumValue {
// Xcode會自動縮進
print("Hello, World!")
}
- 基本上不要通過下標直接訪問數組內容,如果可能使用如
.first
或.last
, 因為這些方法是非強制類型并不會崩潰。(如果需要通過下標訪問數組內容,在使用前要做邊界檢查) - 推薦盡可能使用
for item in items
而不是for i in 0..<items.count
遍歷數組。 - 不要使用
+=
或+
操作符給數組添加新元素,使用性能較好的.append()
或.appendContentsOf()
。 - 如果需要聲明數組基于其他的數組并保持不可變類型, 使用
let myNewArray = [arr1, arr2].flatten()
,而不是let myNewArray = arr1 + arr2
。 - 總體上,我們推薦使用提前返回的策略,而不是
if
語句的嵌套。使用guard
語句可以改善代碼的可讀性。示例:
// 推薦
func eatDoughnut(atIndex index: Int) {
guard index >= 0 && index < doughnuts else {
// 如果 index 超出允許范圍,提前返回。
return
}
let doughnut = doughnuts[index]
eat(doughnut)
}
// 不推薦
func eatDoughnuts(atIndex index: Int) {
if index >= 0 && index < donuts.count {
let doughnut = doughnuts[index]
eat(doughnut)
}
}
- 在解析可選類型時,推薦使用
guard
語句,而不是if
語句,因為guard
語句可以減少不必要的嵌套縮進。示例:
// 推薦
guard let monkeyIsland = monkeyIsland else {
return
}
bookVacation(onIsland: monkeyIsland)
bragAboutVacation(onIsland: monkeyIsland)
// 不推薦
if let monkeyIsland = monkeyIsland {
bookVacation(onIsland: monkeyIsland)
bragAboutVacation(onIsland: monkeyIsland)
}
// 禁止
if monkeyIsland == nil {
return
}
bookVacation(onIsland: monkeyIsland!)
bragAboutVacation(onIsland: monkeyIsland!)
- 如果你不確定
if
語句 和guard
語句哪一個可讀性更強,建議使用guard
。
// if 語句更有可讀性
if operationFailed {
return
}
// guard 語句這里有更好的可讀性
guard isSuccessful else {
return
}
// 雙重否定不易被理解 - 不要這么做
guard !operationFailed else {
return
}
- 如果需要在2個狀態間做出選擇,建議使用
if
語句,而不是使用guard
語句。
// 推薦
if isFriendly {
print("你好, 遠路來的朋友!")
} else {
print("窮小子, 哪兒來的?")
}
// 不推薦
guard isFriendly else {
print("窮小子, 哪兒來的?")
return
}
print("你好, 遠路來的朋友!")
- 使用類型推斷上下文,使用編譯器推斷上下文來編寫簡潔的代碼。
// 推薦
let selector = #selector(viewDidLoad)
view.backgroundColor = .red
let toView = context.view(forKey: .to)
let view = UIView(frame: .zero)
// 不推薦
let selector = #selector(ViewController.viewDidLoad)
view.backgroundColor = UIColor.red
let toView = context.view(forKey: UITransitionContextViewKey.to)
let view = UIView(frame: CGRect.zero)
框架使用
- 項目中所有控件(UIButton、UILabel等)必須使用DJExtension中封裝的初始化方法。
// 帶布局
let checkAllLabel = UILabel(text: "查看全部", font: dj_semiboldFont(14), color: .white, alignment: .right, superView: originalView) { (make) in
make.center.equalTo(thirdImageView)
make.width.equalTo(56)
make.height.equalTo(20)
}
// 不帶布局
let checkAllLabel = UILabel(text: "查看全部", font: dj_semiboldFont(14), color: .white, alignment: .right)
- 項目中所有view或控制器相關操作,必須使用DJExtension中封裝的擴展方法。
// UIView
view.dj_addBottomLine() // 在底部添加分割線
view.dj_addShadow() // 添加陰影
view.dj_removeAllSubviews() // 移除所有子控件
view.dj_getParentViewController() // 獲取父控制器
...
// 控制器
vc.dj_push(TestViewController())
vc.dj_present(TestViewController())
vc.dj_pop()
vc.dj_showActionSheet()
...
- 項目中所有常用的設置值、獲取某個值或判斷,使用DJExtension中封裝的公共函數。如果沒有,可以完善DJExtension庫。
Functions | Comment |
---|---|
dj_hexColor("00ff00") |
get a color with a hex value. |
dj_pingSemiboldFont(15) |
get a font from the font family "PingFangSC-Semibold". |
dj_isCameraAllowed() |
the camera authorization is allowed or not. |
dj_navigationBarHeight() |
get the navigation bar height(adapted iPhone X or later). |
dj_isEmpty(obj) |
an object is empty or not. |
dj_isIPhoneX() |
the phone is iPhone X,Xs,Xs Max or not. |
dj_toJson(obj) |
convert an object to json string. |
dj_callPhone("13288889990") |
call a number |
dj_postNotification("LoginSuccessNotification") |
post a notification. |
more... |
網絡請求
- 項目中的網絡請求數據架構采用Alamofire + Moya + SwiftyJSON + ObjectMapper。
- 項目中處理網絡請求的類,統一命名為: 'xxService',比如‘HomeService’。
- 用來解析數據的模型優先使用
Struct
,Struct不能滿足要求時,使用Class
。
后記
- 此規范主要是自己工作時的總結,可以提高代碼可讀性、可維護性,并可提高開發效率。如果有建議或發現有問題的地方,歡迎批評指正。
Have fun.