一. 格式規(guī)范
1.1 使用4個(gè)空格進(jìn)行縮進(jìn)
推薦
if value == 1 {
print("")
}
1.2 二元運(yùn)算符(+, ==, 或->)的前后都需要添加空格
推薦
let value = 1 + 2
if value == 1 {
/* ... */
}
func test(with value: TestClass) -> returnValue {
/* ... */
}
1.3 一般情況下,在逗號(hào)和冒號(hào)后面加一個(gè)空格
推薦
let array = [1, 2, 3, 4, 5]
let dic: [String: Any] = [:]
不推薦
let array = [1,2,3,4,5]
let dic : [String :Any] = [:]
1.4 switch 每個(gè)case結(jié)尾留一行空行, 最后一行不留. if同理.
推薦
switch i {
case .a:
print("a")
case .b:
print("b")
default:
default
}
if isTrue {
/* ... 結(jié)尾空行 */
} else if isFlase {
/* ... */
} else {
/* ... 最后一行不需要空行 */
}
不推薦
switch i {
case .a:
print("a")
case .b:
print("b")
default:
default
}
if isTrue {
/* ... */
} else if isFlase {
/* ... */
} else {
/* ... */
}
1.5 代碼結(jié)尾不要使用分號(hào);
推薦
print("Hello World")
不推薦
print("Hello World");
1.6 左大括號(hào)不要另起一行
推薦
// 1. Class Define
class TestClass {
/* ... */
}
// 2. if
if value == 1 {
/* ... */
}
// 3. if else
if value == 1 {
/* ... */
} else {
/* ... */
}
// 4. while
while isTrue {
/* ... */
}
// 5. guard
guard let value = value else {
/* ... */
}
不推薦
// 1. Class Define
class TestClass
{
/* ... */
}
// 2. if
if value == 1
{
/* ... */
}
// 3. if else
if value == 1
{
/* ... */
}
else
{
/* ... */
}
// 4. while
while isTrue
{
/* ... */
}
// 5. guard
guard let value = value else
{
/* ... */
}
1.7 判斷語(yǔ)句不用括號(hào)
推薦
if value == 1 {
/* ... */
}
if value == 1 && string == "test" {
/* ... */
}
不推薦
if (value == 1) {
/* ... */
}
if ((value == 1) && (string == "test")) {
/* ... */
}
1.8 不建議使用self. , 除非方法參數(shù)與屬性同名或其他必要情況
推薦
func setup() {
label = UILabel()
}
不推薦
func setup() {
self.label = UILabel()
}
1.9 善用類型推導(dǎo), 不寫(xiě)多余代碼
推薦
func set(color: UIColor) {
/* ... */
}
set(color: .black)
不推薦
func set(color: UIColor) {
/* ... */
}
set(color: UIColor.black)
1.10 添加有必要的注釋,盡可能使用Xcode注釋快捷鍵(??/)
推薦
/// <#Description#>
///
/// - Parameter test: <#test description#>
/// - Returns: <#return value description#>
func test(string: String?) -> String? {
/* ... */
}
不推薦
/// <#Description#>
func test(string: String?) -> String? {
/* ... */
}
1.11 注釋符//后加空格,如果//跟在代碼后面,前面也加一個(gè)空格
推薦
// 注釋
let aString = "xxx" // 注釋
1.12 使用// MARK: -,按功能和協(xié)議 / 代理分組
/// MARK順序沒(méi)有強(qiáng)制要求,但System API & Public API一般分別放在第一塊和第二塊。
// MARK: - Public
// MARK: - Request
// MARK: - Action
// MARK: - Private
// MARK: - xxxDelegate
1.13 對(duì)外接口不兼容時(shí),使用@available(iOS x.0, *)標(biāo)明接口適配起始系統(tǒng)版本號(hào)
@available(iOS x.0, *)
class MyClass {
/* ... */
}
@available(iOS x.0, *)
func myFunction() {
/* ... */
}
1.14 方法間合理?yè)Q行
推薦
extension XXX {
// 擴(kuò)展開(kāi)始 增加一個(gè)換行
func xxxx1() {
/* ... */
}
// 方法之間 增加一個(gè)換行
func xxxx2() {
/* ... */
}
}
1.15 代碼塊合理?yè)Q行
推薦
func xxxx() {
/* ... */
/* ... */
/* ... */
}
二. 命名規(guī)范
2.1 建議不要使用前綴, 善用命名空間.
推薦
HomeViewController
Bundle
不推薦
NEHomeViewController
NSBundle
2.2 不要縮寫(xiě)、簡(jiǎn)寫(xiě)、單個(gè)字母來(lái)命名
推薦
let frame = view.frame
let image = imageView.image
let backgroundColor = view.backgroundColor
不推薦
let f = view.frame
let img = imageView.image
let bgColor = view.backgroundColor
2.3 如非必要, 不要聲明全局常量/變量/函數(shù), 應(yīng)該根據(jù)類型適當(dāng)歸類包裝, 合理利用命名空間.
推薦
enum Main {
static let color = UIColor(red: 0.1, green: 0.2, blue: 0.3, alpha: 1.0)
static let count = 13
static func font(_ size: CGFloat) -> UIFont {
return UIFont(name: "xxx", size: size) ?? .systemFont(ofSize: size)
}
}
不推薦
let mainColor = UIColor(red: 0.1, green: 0.2, blue: 0.3, alpha: 1.0)
let mainCount = 13
func mainFont(_ size: CGFloat) -> UIFont {
return UIFont(name: "xxx", size: size) ?? UIFont.systemFont(ofSize: size)
}
2.4 變量命名
使用小駝峰,首字母小寫(xiě)
變量命名應(yīng)該能推斷出該變量類型,如果不能推斷,則需要以變量類型結(jié)尾
推薦
class TestClass: class {
// UIKit的子類,后綴最好加上類型信息
let coverImageView: UIImageView
@IBOutlet weak var usernameTextField: UITextField!
// 作為屬性名的firstName,明顯是字符串類型,所以不用在命名里不用包含String
let firstName: String
// UIViewContrller以ViewController結(jié)尾
let fromViewController: UIViewController
// 集合類型以復(fù)數(shù)形式命名
var datas: [Data] = []
var items: [Item] = []
}
不推薦
class TestClass: class {
// image不是UIImageView類型
let coverImage: UIImageView
// or cover不能表明其是UIImageView類型
var cover: UIImageView
// String后綴多余
let firstNameString: String
// UIViewContrller不要縮寫(xiě)
let fromVC: UIViewController
// 集合類型多余后綴和描述
var dataList: [Data] = []
var itemArray: [Item] = []
}
2.5 類型命名:使用大駝峰表示法,首字母大寫(xiě)
2.6 方法命名:使用參數(shù)標(biāo)簽讓方法語(yǔ)義更清楚, 參數(shù)標(biāo)簽和參數(shù)需要表達(dá)正確的語(yǔ)義(Public接口及基礎(chǔ)組件必須遵循) (from Swift API Design Guidelines)
2.6.1 省略所有的冗余的外部參數(shù)標(biāo)簽
推薦
func min(_ number1: Int, _ number2: Int) {
/* ... */
}
min(1, 2)
不推薦
func min(number1: Int, number2: Int) {
/* ... */
}
min(number1: 1, number2: 2)
2.6.2 進(jìn)行安全值類型轉(zhuǎn)換的構(gòu)造方法可以省略參數(shù)標(biāo)簽,非安全類型轉(zhuǎn)換則需要添加參數(shù)標(biāo)簽以表示類型轉(zhuǎn)換方法
推薦
extension UInt32 {
/// 安全值類型轉(zhuǎn)換,16位轉(zhuǎn)32位,可省略參數(shù)標(biāo)簽
init(_ value: Int16)
/// 非安全類型轉(zhuǎn)換,64位轉(zhuǎn)32位,不可省略參數(shù)標(biāo)簽
/// 截?cái)囡@示
init(truncating source: UInt64)
/// 非安全類型轉(zhuǎn)換,64位轉(zhuǎn)32位,不可省略參數(shù)標(biāo)簽
/// 顯示最接近的近似值
init(saturating valueToApproximate: UInt64)
}
2.6.3 當(dāng)?shù)谝粋€(gè)參數(shù)構(gòu)成整個(gè)語(yǔ)句的介詞時(shí)(如,at, by, for, in, to, with 等),為第一個(gè)參數(shù)添加介詞參數(shù)標(biāo)簽
推薦
// 添加介詞標(biāo)簽having
func removeBoxes(having length: Int) {
/* ... */
}
let length = 23
x.removeBoxes(having: length)
例外情況是,當(dāng)后面所有參數(shù)構(gòu)成獨(dú)立短語(yǔ)時(shí),則介詞提前。
推薦
// 介詞To提前
a.moveTo(x: b, y: c)
// 介詞From提前
a.fadeFrom(red: b, green: c, blue: d)
不推薦
a.move(toX: b, y: c)
a.fade(fromRed: b, green: c, blue: d)
2.6.4 當(dāng)?shù)谝粋€(gè)參數(shù)構(gòu)成整個(gè)語(yǔ)句一部分時(shí),省略第一個(gè)參數(shù)標(biāo)簽,否則需要添加第一個(gè)參數(shù)標(biāo)簽.
推薦
// 參數(shù)構(gòu)成語(yǔ)句一部分,省略第一個(gè)參數(shù)標(biāo)簽
view.addSubview(tempView)
// 參數(shù)不構(gòu)成語(yǔ)句一部分,不省略第一個(gè)參數(shù)標(biāo)簽
dismiss(animated: false)
2.6.5 其余情況下,給除第一個(gè)參數(shù)外的參數(shù)都添加標(biāo)簽
2.7 方法命名:不要使用冗余的單詞,特別是與參數(shù)及參數(shù)標(biāo)簽重復(fù)
推薦
func remove(_ member: Element) -> Element?
不推薦
func removeElement(_ member: Element) -> Element?
2.8 命名出現(xiàn)縮寫(xiě)詞,縮寫(xiě)詞要么全部大寫(xiě),要么全部小寫(xiě),以首字母大小寫(xiě)為準(zhǔn)
推薦
let urlRouterString = "https://xxxxx"
let htmlString = "xxxx"
class HTMLModel {
/* ... */
}
struct URLRouter {
/* ... */
}
不推薦
let uRLRouterString = "https://xxxxx"
let hTMLString = "xxxx"
class HtmlModel {
/* ... */
}
struct UrlRouter {
/* ... */
}
2.9 Bool類型命名:用is為前綴
推薦
var isString: Bool = true
2.10 枚舉定義盡量簡(jiǎn)寫(xiě),不要包括類型前綴
推薦
public enum UITableViewRowAnimation: Int {
case fade
case right // slide in from right (or out to right)
case left
case top
case bottom
case none // available in iOS 3.0
case middle // available in iOS 3.2. attempts to keep cell centered in the space it will/did occupy
case automatic // available in iOS 5.0. chooses an appropriate animation style for you
}
2.11 協(xié)議命名 (from Swift API Design Guidelines)
2.11.1 如果協(xié)議描述的是協(xié)議做的事應(yīng)該命名為名詞(eg. Collection)
推薦
protocol TableViewSectionProvider {
func rowHeight(at row: Int) -> CGFloat
var numberOfRows: Int { get }
/* ... */
}
2.11.2 如果協(xié)議描述的是能力,需添加后綴able或 ing (eg. Equatable、 ProgressReporting)
推薦
protocol Loggable {
func logCurrentState()
/* ... */
}
protocol Equatable {
func ==(lhs: Self, rhs: Self) -> Bool {
/* ... */
}
}
2.11.3 如果已經(jīng)定義類,需要給類定義相關(guān)協(xié)議,則添加Protocol后綴
推薦
protocol InputTextViewProtocol {
func sendTrackingEvent()
func inputText() -> String
/* ... */
}
2.11.4 如果已經(jīng)定義類,需要給類定義相關(guān)委托協(xié)議,則添加Delegate后綴
推薦
public protocol UITabBarControllerDelegate: NSObjectProtocol {
@available(iOS 3.0, *)
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool
}
三. 語(yǔ)法規(guī)范
3.1 多使用let,少使用var
3.2 少用!去強(qiáng)制解包
3.3 可選類型拆包取值時(shí),使用if let判斷
推薦
if let optionalValue = optionalValue {
/* ... */
}
杜絕
if optionalValue != nil {
let value = optionalValue!
/* ... */
}
3.4 多個(gè)可選類型拆包取值時(shí),將多個(gè)if let合并, 除非特殊邏輯需要.
推薦
var subview: UIView?
var volume: Double?
if let subview = subview, let volume = volume {
/* ... */
}
不推薦
var optionalSubview: UIView?
var volume: Double?
if let unwrappedSubview = optionalSubview {
if let realVolume = volume {
/* ... */
}
}
3.5 不要使用 as! 或 try!
推薦
// 使用if let as?判斷
if let text = text as? String {
/* ... */
}
// 使用if let try 或者 try?
if let test = try aTryFuncton() {
/* ... */
}
3.6 數(shù)組和字典變量定義,定義時(shí)需要標(biāo)明泛型類型,并使用更簡(jiǎn)潔的語(yǔ)法.
推薦
var names: [String] = []
var lookup: [String: Int] = [:]
不推薦
var names = [String]()
var names: Array<String> = [String]() / 不夠簡(jiǎn)潔
var lookup = [String: Int]()
var lookup: Dictionary<String, Int> =Dictionary<String, Int>() // 不夠簡(jiǎn)潔
3.7 數(shù)組訪問(wèn)盡可能使用 .first 或 .last, 推薦使用 for item in items 或 items.forEach { } 而不是 for i in 0...X
推薦
items.first
items.last
for item in items {
/* ... */
}
items.forEach {
/* ... */
}
不推薦
items[0]
items[items.count - 1]
for i in 0 ..< items.count {
let item = items[i]
/* ... */
}
3.8 如果變量能夠推斷出類型,則不建議聲明變量時(shí)指明類型
推薦
let value = 1
let text = "xxxx"
不推薦
let value: Int = 1
let text: String = "xxxx"
3.9 判斷相等
3.9.1 使用==和!=判斷內(nèi)容上是否一致
推薦
// String類型沒(méi)有-isEqualToString方法,用==判斷是否相等
let str1 = "netease"
let str2 = "netease"
if str1 == str2 {
// is true
/* ... */
}
// 對(duì)于自定義類型,判斷內(nèi)容是否一致,需要實(shí)現(xiàn)Equatable接口
class BookItem {
let bookId: String
let title: String
init (bookId: String, title: String) {
self.bookId = bookId
self.title = title
}
}
extension BookItem: Equatable {
static func ==(lhs: BookItem, rhs: BookItem) -> Bool {
// 具體判斷規(guī)則根據(jù)實(shí)際需要進(jìn)行
return lhs.bookId == rhs.bookId
}
}
3.9.2 使用===和!==判斷class類型對(duì)象是否同一個(gè)引用,而不是用==和!=
推薦
if tenEighty === alsoTenEighty {
/* ... */
}
if tenEighty !== notTenEighty {
/* ... */
}
3.10 協(xié)議
3.10.1 當(dāng)實(shí)現(xiàn)protocol時(shí),如果確定protocol的實(shí)現(xiàn)不會(huì)被重寫(xiě),建議用extension將protocol實(shí)現(xiàn)分離
推薦
class MyViewController: UIViewController {
// class stuff here
}
// MARK: - UITableViewDataSource
extension MyViewController: UITableViewDataSource {
// table view data source methods
}
// MARK: - UIScrollViewDelegate
extension MyViewController: UIScrollViewDelegate {
// scroll view delegate methods
}
不推薦
class MyViewController: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
// all methods
}
3.12 當(dāng)方法最后一個(gè)參數(shù)是Closure類型,調(diào)用時(shí)建議使用尾隨閉包語(yǔ)法, 但只在只存在一個(gè)閉包參數(shù)時(shí)才使用尾閉包。
推薦
UIView.animateWithDuration(1.0) {
self.myView.alpha = 0
}
UIView.animateWithDuration(1.0,
animations: {
self.myView.alpha = 0
},
completion: { finished in
self.myView.removeFromSuperview()
}
)
不推薦
UIView.animateWithDuration(1.0, animations: {
self.myView.alpha = 0
})
UIView.animateWithDuration(1.0,
animations: {
self.myView.alpha = 0
}) { finished in
self.myView.removeFromSuperview()
}
3.13 高階函數(shù)推薦最簡(jiǎn)化語(yǔ)法
推薦
array.sort(by: <)
array.sort { $0.age < $1.age }
不推薦
array.sort { (l, r) -> Bool in
l < r
}
array.sort { (l, r) -> Bool in
return l < r
}
3.14 訪問(wèn)控制 (優(yōu)先考慮最低級(jí))
privatefileprivateinternal(默認(rèn)忽略不寫(xiě))publicopen
訪問(wèn)控制權(quán)限關(guān)鍵字應(yīng)該寫(xiě)在最前面,除了@IBOutlet、@IBAction、@discardableResult、@objc等關(guān)鍵字.
推薦
// 類似注解修飾詞單獨(dú)占一行
@objc
func print(message: String) -> String {
/* ... */
return xxx
}
3.15 如調(diào)用者可以不使用方法的返回值,則需要使用@discardableResult標(biāo)明
推薦
@discardableResult
func print(message: String) -> String {
let output = "Output : \(message)"
print(output)
return output
}
3.16 Golden Path,最短路徑原則
推薦
func test(_ number1: Int?, _ number2: Int?, _ number3: Int?) {
guard
let number1 = number1,
number2 = number2,
number3 = number3 else {
fatalError("impossible")
}
/* ... */
}
func login(with username: String?, password: String?) throws -> LoginError {
guard let username = username else {
throw .noUsername
}
guard let password = password else {
throw .noPassword
}
/* login code */
}
不推薦
func test(_ number1: Int?, _ number2: Int?, _ number3: Int?) {
if let number1 = number1 {
if let number2 = number2 {
if let number3 = number3 {
/* ... */
} else {
fatalError("impossible")
}
} else {
fatalError("impossible")
}
} else {
fatalError("impossible")
}
}
func login(with username: String?, password: String?) throws -> LoginError {
if let username = username {
if let password = password {
/* login code */
} else {
throw .noPassword
}
} else {
throw .noUsername
}
}
3.17 循環(huán)引用
3.17.1 使用委托和協(xié)議時(shí),避免循環(huán)引用,定義屬性的時(shí)候使用weak修飾
推薦
public weak var dataSource: UITableViewDataSource?
public weak var delegate: UITableViewDelegate?
3.17.2 在逃逸Closures中使用self時(shí)避免循環(huán)引用
推薦
request(.list) { [weak self] (result: Result<[Model]>) in
guard let self = self else { return }
self.items = self.result.value
self.tableView.reloadData()
}
不推薦
request(.list) { [unowned self] (result: Result<[Model]>) in
self.items = self.result.value
self.tableView.reloadData()
}
不推薦
request(.list) { [weak self] (result: Result<[Model]>) in
self?.items = self?.result.value
self?.tableView.reloadData()
}
3.17.3 使用方法作為閉包參數(shù)時(shí), 應(yīng)注意循環(huán)引用問(wèn)題
不推薦
func abc() {
// 內(nèi)嵌方法
func close() {
// 訪問(wèn)了外部self 存在循環(huán)引用
self.controller.dismiss()
}
// 作為閉包參數(shù)
view.set(close)
xxxx.closeHandle = close
}
推薦
func abc() {
weak var `self` = self
// 內(nèi)嵌方法
func close() {
// 訪問(wèn)weak修飾的self 不存在循環(huán)引用
self?.controller.dismiss()
}
// 作為閉包參數(shù)
view.set(close)
xxxx.closeHandle = close
}
??同樣上面的代碼如果func close() 不是內(nèi)嵌方法, 而是與func abc()同級(jí)的方法, 那么必然存在循環(huán)引用, 需要重點(diǎn)注意.
不推薦
func close() {
self.controller.dismiss()
}
func abc() {
// 作為閉包參數(shù)
view.set(close)
xxxx.closeHandle = close
}
如果一定要使用同級(jí)的方法可用以下方法暫時(shí)解決:
推薦
func close() {
self.controller.dismiss()
}
func abc() {
weak var `self` = self
// 內(nèi)嵌方法
func close() {
self?.close()
}
// 作為閉包參數(shù)
view.set(close)
xxxx.closeHandle = close
}
3.18 空判斷
推薦
if array.isEmpty {
/* ... */
}
if string.isEmpty {
/* ... */
}
不推薦
if array.count == 0 {
/* ... */
}
if string.count == 0 {
/* ... */
}
3.19 單例
推薦
class TestManager {
static let shared = TestManager()
/* ... */
}