v 1.0 版本
严重参考
一、代码结构
1.1 通用
- 一个
tab
也就是一个缩进四个空格。 - 项目中所有文件中代码的逻辑顺序应该保持一致。将相同功能逻辑的代码块放到单独的扩展当中,每个扩展都应该用
// MARK: -
分割开。在每个// MARK: -
可用// MARK:
再来细分业务。 - 每行最多有一条语句,除非结尾是包含一条语句或不包含语句的代码块。 例:
// 这里只是可以这样写,也可以多行。 guard let value = value else { return 0 } defer { file.close() } switch someEnum { case .first: return 5 case .second: return 10 case .third: return 20 } let squares = numbers.map { $0 * $0 } var someProperty: Int { get { return otherObject.property } set { otherObject.property = newValue } } var someProperty: Int { return otherObject.somethingElse() } required init?(coder aDecoder: NSCoder) { fatalError("no coder") } 复制代码
1.2 换行
如果声明、语句或者表达式适合一行,就写在一行。
1.2.1 方法和函数
-
a.逗号分隔的代码语句,同时只采用水平和垂直中的一种来呈现代码。
-
b.换行以不可分隔的
>(
、) ->
等等和关键字例如where
等等开始时,不缩进。 -
c.逗号分隔的代码语句,换行后需要一个缩进。
-
d.当
{
符号在一条没有缩进的语句后时,不换行,反之,换行。标记
abcd
,在例子相应位置说明。未换行时,例:
public func index<Elements: Collection, Element>(of element: Element, in collection: Elements) -> Elements.Index? where Elements.Element == Element, Element: Equatable { // ... } 复制代码
换行后,例:
public func index<Elements: Collection, Element>( // 规则a和c of element: Element, in collection: Elements ) -> Elements.Index? // 规则b where Elements.Element == Element, Element: Equatable // 规则d { for current in elements { // ... } } 复制代码
上面这个例子中逗号分隔的代码语句是按照垂直方向调整的,你也可以写在一行中。例:
public func index<Elements: Collection, Element>( of element: Element, in collection: Elements ) -> Elements.Index? where Elements.Element == Element, Element: Equatable { for current in elements { // ... } } 复制代码
-
协议中的方法结尾的括号可换行也可以不换行
例:
public protocol ContrivedExampleDelegate { func contrivedExample( _ contrivedExample: ContrivedExample, willDoSomethingTo someValue: SomeValue) } public protocol ContrivedExampleDelegate { func contrivedExample( _ contrivedExample: ContrivedExample, willDoSomethingTo someValue: SomeValue ) } 复制代码
1.2.2 类和扩展的声明
和上面原则类似。
例:
class MyContainer<BaseCollection>:
MyContainerSuperclass,
MyContainerProtocol,
SomeoneElsesContainerProtocol,
SomeFrameworkContainerProtocol
where
BaseCollection: Collection,
BaseCollection.Element: Equatable,
BaseCollection.Element: SomeOtherProtocolOnlyUsedToForceLineWrapping
{
// ...
}
复制代码
1.2.3 方法调用
-
每一个参数写在一行,并使用一个缩进,如果函数没有尾随闭包,那么
)
可以换行也可以不换。let index = index( of: veryLongElementVariableName, in: aCollectionOfElementsThatAlsoHappensToHaveALongName) let index = index( of: veryLongElementVariableName, in: aCollectionOfElementsThatAlsoHappensToHaveALongName ) 复制代码
-
调用多个参数的函数时,如果参数传值是数组、字典和闭包等等,如下例缩进。
例:
someFunctionWithABunchOfArguments( someStringArgument: "hello I am a string", someArrayArgument: [ "dadada daaaa daaaa dadada daaaa daaaa dadada daaaa daaaa", "string one is crazy - what is it thinking?" ], someDictionaryArgument: [ "dictionary key 1": "some value 1, but also some more text here", "dictionary key 2": "some value 2" ], someClosure: { parameter1 in print(parameter1) }) 复制代码
1.2.4 控制流语句
if
, guard
, while
, for
等等。
-
a.条件语句换行后应该和第一条条件语句保持同一缩进。
-
b.如果是当前平行的语法元素,就同一位置缩进,如果是嵌套的语法元素,则使用一个缩进。
-
c.开括号保持当前行或者换行都是可以的,但
guard
语句中的else {
需要保持在一行。标记
abc
在例子中说明。例:
if aBooleanValueReturnedByAVeryLongOptionalThing() && // a aDifferentBooleanValueReturnedByAVeryLongOptionalThing() && yetAnotherBooleanValueThatContributesToTheWrapping() { doSomething() } if aBooleanValueReturnedByAVeryLongOptionalThing() && aDifferentBooleanValueReturnedByAVeryLongOptionalThing() && yetAnotherBooleanValueThatContributesToTheWrapping() { doSomething() } if let value = aValueReturnedByAVeryLongOptionalThing(), let value2 = aDifferentValueReturnedByAVeryLongOptionalThing() { doSomething() } guard let value = aValueReturnedByAVeryLongOptionalThing(), let value2 = aDifferentValueReturnedByAVeryLongOptionalThing() else { doSomething() } guard let value = aValueReturnedByAVeryLongOptionalThing(), let value2 = aDifferentValueReturnedByAVeryLongOptionalThing() // c. else { doSomething() } for element in collection // b where element.happensToHaveAVeryLongPropertyNameThatYouNeedToCheck { doSomething() } 复制代码
1.3 语句中的间隔
用一个空格分隔
-
如果条件语句或选择语句(如
if
、guard
、When
或Switch
)等等以圆括号(
开头,则在该语句的关键字与其后面的表达式之间空一格。例:
// 推荐 if (x == 0 && y == 0) || z == 0 { // ... } // 不推荐 if(x == 0 && y == 0) || z == 0 { // ... } 复制代码
-
同一行代码中有
{ }
样式的代码块时,{
后和}
前需要空一格,{
前也需要空一格。例:
// 推荐 let nonNegativeCubes = numbers.map { $0 * $0 * $0 }.filter { $0 >= 0 } // 不推荐 let nonNegativeCubes = numbers.map { $0 * $0 * $0 } .filter { $0 >= 0 } let nonNegativeCubes = numbers.map{$0 * $0 * $0}.filter{$0 >= 0} 复制代码
-
在任何二元或三元运算符的两侧(
+
和-
等等),及一些类似运算符的两侧(详细如下)。-
在赋值变量和属性及初始化方法的默认参数中使用的
=
符号两侧。例:
// 推荐 var x = 5 func sum(_ numbers: [Int], initialValue: Int = 0) { // ... } // 不推荐 var x=5 func sum(_ numbers: [Int], initialValue: Int=0) { // ... } 复制代码
-
协议组合类型中的
&
号两侧。例:
// 推荐 func sayHappyBirthday(to person: NameProviding & AgeProviding) { // ... } // 不推荐 func sayHappyBirthday(to person: NameProviding&AgeProviding) { // ... } 复制代码
-
在自定义运算符函数中的自定义自定义运算符两侧。
例:
// 推荐 static func == (lhs: MyType, rhs: MyType) -> Bool { // ... } // 不推荐 static func ==(lhs: MyType, rhs: MyType) -> Bool { // ... } 复制代码
-
-
方法和函数后返回类型前的箭头 (
->
) 两侧。例:
// 推荐 func sum(_ numbers: [Int]) -> Int { // ... } // 不推荐 func sum(_ numbers: [Int])->Int { // ... } 复制代码
-
在元组、数组及字典中的
,
后,括号内侧不需要空一格。例:
// 推荐 let numbers = [1, 2, 3] // 不推荐 let numbers = [1,2,3] let numbers = [1 ,2 ,3] let numbers = [1 , 2 , 3] let numbers = [ 1, 2, 3 ] 复制代码
-
在如下几种情况的
:
后空一格:-
声明父类、遵守协议及泛型约束中。
例:
// 推荐 struct HashTable: Collection { // ... } struct AnyEquatable<Wrapped: Equatable>: Equatable { // ... } // 不推荐 struct HashTable : Collection { // ... } struct AnyEquatable<Wrapped : Equatable> : Equatable { // ... } 复制代码
-
声明指定变量和属性的类型时。
例:
// 不推荐 let number: Int = 5 // 推荐 let number:Int = 5 let number : Int = 5 复制代码
-
指定元组和函数参数的类型时。
例:
// 推荐 let tuple: (x: Int, y: Int) func sum(_ numbers: [Int]) { // ... } // 不推荐 let tuple: (x:Int, y:Int) let tuple: (x : Int, y : Int) func sum(_ numbers:[Int]) { // ... } func sum(_ numbers : [Int]) { // ... } 复制代码
-
指定字典
value
及其类型时。例:
// 推荐 var nameAgeMap: [String: Int] = [] let nameAgeMap = ["Ed": 40, "Timmy": 9] // 不推荐 var nameAgeMap: [String:Int] = [] var nameAgeMap: [String : Int] = [] let nameAgeMap = ["Ed":40, "Timmy":9] let nameAgeMap = ["Ed" : 40, "Timmy" : 9] 复制代码
-
-
在当前行尾部注释时,
//
前至少要有两个空格,//
只需要一个空格。例:
// 推荐 let initialFactor = 2 // Warm up the modulator. // 不推荐 let initialFactor = 2 // Warm up the modulator. 复制代码
1.4 代码间的空行
- 最好不要使用多个空行。
- 根据代码语句之间的逻辑空行。
1.5 缩进
1.5.1 Switch语句
-
case
语句和switch
关键字保持想通缩进,case
中的语句使用一个缩进。例:
// 推荐 switch order { case .ascending: print("Ascending") case .descending: print("Descending") case .same: print("Same") } // 不推荐 switch order { case .ascending: print("Ascending") case .descending: print("Descending") case .same: print("Same") } // 不推荐 switch order { case .ascending: print("Ascending") case .descending: print("Descending") case .same: print("Same") } 复制代码
1.6 圆括号
-
if
,guard
,while
, 和switch
关键字后的顶层表达式中不应该使用圆括号。例:
// 推荐 if x == 0 { print("x is zero") } if (x == 0 || y == 1) && z == 2 { print("...") } // 不推荐 if (x == 0) { print("x is zero") } if ((x == 0 || y == 1) && z == 2) { print("...") } 复制代码
二、命名
2.1 文件命名
- 文件中只包含一个
MyType
类,取名MyTyle.swift
。 - 文件中包含一个
MyType
类和一些顶层辅助函数,取名为MyType.swift
。 - 文件中只包含一个
MyType
类的extension
, 并遵守了一个MyProtocol
协议,取名MyType+MyProtocol.swift
。 - 文件中包含了一个
MyType
类的多个extension
来增加了辅助功能等,取名类似为MyType+Additions.swift
。 - 文件中包含的是一些同类型的全局方法,例如一些数学运算的方法,可以取名为
Math.swift
。
2.2 大小写
-
类型 (类,结构体,枚举和协议) 命名应该是
UpperCamelCase
的形式,其他所有的命名全部是lowerCamelCase
形式。 -
一些缩略词命名在开始时,全部小写,如果在中间,全部大写。例如
URL
、JSON
和HTML
等等。例:
// HTML 在开头,所以命名为html let htmlBodyContent: String = "<p>Hello, World!</p>" let profileID: Int = 1 class URLFinder { // ... } 复制代码
2.3 缩写和歧义
-
不要使用缩写及避免歧义。
例:
// 推荐 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 } } 复制代码
// 推荐 class ConnectionTableViewCell: UITableViewCell { let personImageView: UIImageView let animationDuration: TimeInterval // String 类型在很明显的意思的时候可以不用加后缀 let firstName: String let popupViewController: UIViewController let popupTableViewController: UITableViewController @IBOutlet weak var submitButton: UIButton! @IBOutlet weak var emailTextField: UITextField! @IBOutlet weak var nameLabel: UILabel! } // 不推荐 class ConnectionTableViewCell: UITableViewCell { let personImage: UIImageView let text: UILabel // 使用`animationDuration` 和 `animationTimeInterval`。 let animation: TimeInterval // 看上去不明显,使用 `transitionText` 或者 `transitionString`。 let transition: String let popupView: UIViewController let popupVC: UIViewController // 使用 `TableViewController` let popupViewController: UITableViewController // 将类型名字添加在后缀上,并不要使用缩写 @IBOutlet weak var btnSubmit: UIButton! @IBOutlet weak var buttonSubmit: UIButton! // 使用`firstNameLabel` @IBOutlet weak var firstName: UILabel! } 复制代码
2.4 协议
- 如果是描述的是要做什么,使用名词,如果描述的是一个功能或者能力,使用
able
,ible
, 和ing
等后缀。如果不是上面两种情况,使用Protocol
后缀。 例:// 描述做什么的,使用了名词 protocol TableViewSectionProvider { func rowHeight(at row: Int) -> CGFloat var numberOfRows: Int { get } // ... } // 描述日志功能,所以用了 able 后缀 protocol Loggable { func logCurrentState() // ... } // 不是上诉情况,用了 `Protocol` 后缀 protocol InputTextViewProtocol { func sendTrackingEvent() func inputText() -> String // ... } 复制代码
2.5 枚举
-
枚举值使用小写开头。
例:
enum Shape { case rectangle case square case rightTriangle case equilateralTriangle } 复制代码
2.6 初始化方法
-
初始化方法中参数名应该和储存属性的命名一样,并在初始化方法中使用
self.
来区分他们。例:
// 推荐 public struct Person { public let name: String public let phoneNumber: String public init(name: String, phoneNumber: String) { self.name = name self.phoneNumber = phoneNumber } } // 不推荐 public struct Person { public let name: String public let phoneNumber: String public init(name otherName: String, phoneNumber otherPhoneNumber: String) { name = otherName phoneNumber = otherPhoneNumber } } 复制代码
2.7 类属性
-
当使用类属性初始化时,不应该重复类似类名的后缀。
例:
// 推荐 public class UIColor { public class var red: UIColor { // ... } } public class URLSession { public class var shared: URLSession { // ... } } // 不推荐 public class UIColor { public class var redColor: UIColor { // ... } } public class URLSession { public class var sharedSession: URLSession { // ... } } 复制代码
-
当使用类属性作为单例使用时,应该用达成共识的
shared
和default
等来命名。
2.8 全局常量
-
全局变量采用
lowerCamelCase
样式,并不使用类似k
和g
等等前缀。例:
// 推荐 let secondsPerMinute = 60 // 不推荐 let SecondsPerMinute = 60 let kSecondsPerMinute = 60 let gSecondsPerMinute = 60 let SECONDS_PER_MINUTE = 60 复制代码
2.9 代理方法
主要参考 Cocoa
框架的命名风格,所有代理方法将委托的源对象作为第一个参数。
委托的源对象: UITableView 就是 UITableViewDelegate 代理方法中的委托源对象。
-
对于只有一个委托源对象的参数的代理方法。
-
如果是返回为
Void
并且用来响应某个事件发生的代理方法,用委托源对象的类型为基本名字,然后加上描述该事件的动词短语。不使用函数标签。例:
func scrollViewDidBeginScrolling(_ scrollView: UIScrollView) 复制代码
-
如果是返回为
Bool
类型来进行断言操作的代理方法,用委托源对象的类型为基本名字,然后加上描述该断言的动词短语, 不使用函数标签。例:
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool 复制代码
-
如果是返回其他类型的代理方法,用描述需要类型的属性名为基本名字。使用适当的介词作为参数标签来描述属性和委托的源对象的关系。
例:
func numberOfSections(in scrollView: UIScrollView) -> Int 复制代码
-
-
对于其他情况,代理方法的基本命名为源委托对象的类型,并且源委托对象作为第一个参数并且没有标签。
-
如果是返回
Void
的代理方法,第二个参数的参数标签用描述该参数对象的短语或者使用介词描述和其他对象的关系。后面参数同理依次命名。例:
func tableView( _ tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAt indexPath: IndexPath) 复制代码
-
如果是返回
Bool
的代理方法,第二个参数标签使用描述返回布尔值作用的短语来命名,后续参数可以使用介词来提供其他上下文等等。例:
func tableView( _ tableView: UITableView, shouldSpringLoadRowAt indexPath: IndexPath, with context: UISpringLoadedInteractionContext ) -> Bool 复制代码
-
如果返回其他类型,第二个参数标签使用描述返回类型作用的短语和尾介词来命名,后续参数可以使用介词来提供其他上下文等等。
例:
func tableView( _ tableView: UITableView, heightForRowAt indexPath: IndexPath ) -> CGFloat 复制代码
-
三、代码风格
3.1 非文档注释
- 非文档注释使用
//
符号,不要使用/ * */
符号。
3.2 属性
-
在第一次使用局部变量的位置附近声明局部变量(在合理范围内),以最小化它们的作用域。
-
除了元组析构之外,每个let或var语句(无论是属性还是局部变量)都只声明一个变量。
例:
// 推荐 var a = 5 var b = 10 let (quotient, remainder) = divide(100, 9) // 不推荐 var a = 5, b = 10 复制代码
3.3 枚举
-
一般情况每行只有一条
case
语句。但如果没有关联属性和原始值的情况下,可以用逗号分隔写在一行。、例:
public enum Token { case comma case semicolon case identifier } public enum Token { case comma, semicolon, identifier } public enum Token { case comma case semicolon case identifier(String) } public enum Token { case comma, semicolon, identifier(String) } 复制代码
-
当枚举的所有
case
语句都是需要递归的,那么需要把indirect
关键字声明在enum
上。例:
// 推荐 public indirect enum DependencyGraphNode { case userDefined(dependencies: [DependencyGraphNode]) case synthesized(dependencies: [DependencyGraphNode]) } // 不推荐 public enum DependencyGraphNode { indirect case userDefined(dependencies: [DependencyGraphNode]) indirect case synthesized(dependencies: [DependencyGraphNode]) } 复制代码
-
当枚举的
case
语句没有关联值时,不要使用空括号。例:
// 推荐 public enum BinaryTree<Element> { indirect case node(element: Element, left: BinaryTree, right: BinaryTree) case empty } // 不推荐 public enum BinaryTree<Element> { indirect case node(element: Element, left: BinaryTree, right: BinaryTree) case empty() } 复制代码
-
枚举
case
定义的顺序需要按照一定的逻辑,如果没有明显的逻辑,就按照词典顺序排列(字母)。例:
// 推荐 // 根据状态码的原始值排序 public enum HTTPStatus: Int { case ok = 200 case badRequest = 400 case notAuthorized = 401 case paymentRequired = 402 case forbidden = 403 case notFound = 404 case internalServerError = 500 } // 不推荐 public enum HTTPStatus: Int { case badRequest = 400 case forbidden = 403 case internalServerError = 500 case notAuthorized = 401 case notFound = 404 case ok = 200 case paymentRequired = 402 } 复制代码
3.4 尾随闭包
-
函数不应该重载为只有尾随闭包的参数名不一样,因为尾随闭包会隐藏参数名,容易引起歧义。
例:
// 推荐 func greetEnthusiastically(_ nameProvider: () -> String) { print("Hello, \(nameProvider())! It's a pleasure to see you!") } func greetApathetically(_ nameProvider: () -> String) { print("Oh, look. It's \(nameProvider()).") } greetEnthusiastically { "John" } greetApathetically { "not John" } // 不推荐 func greet(enthusiastically nameProvider: () -> String) { print("Hello, \(nameProvider())! It's a pleasure to see you!") } func greet(apathetically nameProvider: () -> String) { print("Oh, look. It's \(nameProvider()).") } greet { "John" } 复制代码
-
如果函数中有多个闭包参数,为了避免歧义时,则不使用尾随闭包语法。
例:
// 推荐 UIView.animate( withDuration: 0.5, animations: { // ... }, completion: { finished in // ... }) // 不推荐 UIView.animate( withDuration: 0.5, animations: { // ... }) { finished in // ... } 复制代码
3.5 标识关键字
-
@availability(...)
和@objc(...)
这类带有参数的标识关键字需要写在修饰语句的前一行并且和修饰代码的缩进一样。不带参数的@objc
@IBOutlet
和@NSManaged
这类无参数的标识关键字需要和修饰语句写在同一行。例:
// 推荐 @available(iOS 9.0, *) public func coolNewFeature() { // ... } public class MyViewController: UIViewController { @IBOutlet private var tableView: UITableView! } // 不推荐 @available(iOS 9.0, *) public func coolNewFeature() { // ... } 复制代码
四、编程风格
4.1 编译警告
-消除除了弃用 API
的其他任何能够消除的警告。
4.2 属性声明,
- 能使用 let 就不使用 var。
- 声明常量和变量时,能使用类型推断就使用。
- 如果一个方法没有参数,仅仅是返回某个值,优先使用计算属性
- 如果是
internal
访问权限就不写,因为他是默认的
4.3 高阶函数
- 优先使用高阶函数 例:
// 推荐 let stringOfInts = [1, 2, 3].flatMap { String($0) } // ["1", "2", "3"] // 不推荐 var stringOfInts: [String] = [] for integer in [1, 2, 3] { stringOfInts.append(String(integer)) } // 推荐 let evenNumbers = [4, 8, 15, 16, 23, 42].filter { $0 % 2 == 0 } // [4, 8, 16, 42] // 不推荐 var evenNumbers: [Int] = [] for integer in [4, 8, 15, 16, 23, 42] { if integer % 2 == 0 { evenNumbers.append(integer) } } 复制代码
4.4 初始化
-
当结构体能够使用字面量初始化时,不要显示的调用初始化方法。
例:
// 推荐 struct Kilometers: ExpressibleByIntegerLiteral { init(integerLiteral value: Int) { // ... } } let k1: Kilometers = 10 let k2 = 10 as Kilometers // 不推荐 struct Kilometers: ExpressibleByIntegerLiteral { init(integerLiteral value: Int) { // ... } } let k = Kilometers(integerLiteral: 10) 复制代码
-
.init(...)
只在使用metatype
变量时使用。例:
let x = MyType(arguments) let type = lookupType(context) let x = type.init(arguments) let x = makeValue(factory: MyType.init) let x = MyType.init(arguments) 复制代码
4.5 属性
-
只读的计算属性省略掉
get
。例:
// 推荐 var totalCost: Int { return items.sum { $0.cost } } // 不推荐 var totalCost: Int { get { return items.sum { $0.cost } } } 复制代码
4.6 Void 和 ()
-
Void
是()
的别名,所以他们的作用是一样的。但是在声明一个闭包或引用一个函数类型时,返回类型使用Void
,在方法声明时,省略,空参数使用()
。例:
// 推荐 func doSomething() { // ... } let callback: () -> Void // 不推荐 func doSomething() -> Void { // ... } func doSomething2() -> () { // ... } let callback: () -> () 复制代码
4.7 可选值
-
不要使用哨兵值。
例如从集合类型里面查找一个值时,没有查找返回缺省值:
// 推荐 func index(of thing: Thing, in things: [Thing]) -> Int? { // ... } if let index = index(of: thing, in: lotsOfThings) { // ... } else { // ... } // 不推荐 func index(of thing: Thing, in things: [Thing]) -> Int { // ... } let index = index(of: thing, in: lotsOfThings) // 使用了哨兵值 -1 if index != -1 { // ... } else { // ... } 复制代码
-
可选值可以用来表示一个错误状态。
例如将一个字符串转换为数字时,转换失败:
struct Int17 { init?(_ string: String) { // ... } } 复制代码
-
当判断一个可选值不是
nil
时,应该直接将它于nil
进行比较。例:
// 推荐 if value != nil { print("value was not nil") } // 不推荐 if let _ = value { print("value was not nil") } 复制代码
-
尽量不要使用非声明情况的隐式(强制)解包。
4.8 错误处理
-
当有多种错误情况时,使用
Error
进行错误处理,并使用do-catch和try
相关语法。例:
struct Document { enum ReadError: Error { case notFound case permissionDenied case malformedHeader } init(path: String) throws { // ... } } do { let document = try Document(path: "important.data") } catch Document.ReadError.notFound { // ... } catch Document.ReadError.permissionDenied { // ... } catch { // ... } 复制代码
-
并且尽量不要使用
try!
,除非是测试代码和外部原因引起的错误。例:
// 这里只会因为编程者把 pattern 输入错误时才会失败 let regex = try! NSRegularExpression(pattern: "a*b+c?") 复制代码
4.9 嵌套类型
-
当
enums
,structs
或classes
和某个类相关时,可以将它嵌套进这个类。例:
// 推荐 class Parser { enum Error: Swift.Error { case invalidToken(String) case unexpectedEOF } func parse(text: String) throws { // ... } } // 不推荐 class Parser { func parse(text: String) throws { // ... } } enum ParseError: Error { case invalidToken(String) case unexpectedEOF } 复制代码
-
声明没有
case
语句的嵌套enum
可以来组合一个常量和工具方法。例:
// 推荐 enum Dimensions { static let tileMargin: CGFloat = 8 static let tilePadding: CGFloat = 4 static let tileContentSize: CGSize(width: 80, height: 64) } // 不推荐 struct Dimensions { private init() {} static let tileMargin: CGFloat = 8 static let tilePadding: CGFloat = 4 static let tileContentSize: CGSize(width: 80, height: 64) } 复制代码
4.10 guard
-
对于一些提前退出的情况,使用
guard
来避免代码嵌套过多。例:
// 推荐 func discombobulate(_ values: [Int]) throws -> Int { guard let first = values.first else { throw DiscombobulationError.arrayWasEmpty } guard first >= 0 else { throw DiscombobulationError.negativeEnergy } var result = 0 for value in values { result += invertedCombobulatoryFactory(of: value) } return result } // 不推荐 func discombobulate(_ values: [Int]) throws -> Int { if let first = values.first { if first >= 0 { var result = 0 for value in values { result += invertedCombobulatoryFactor(of: value) } return result } else { throw DiscombobulationError.negativeEnergy } } else { throw DiscombobulationError.arrayWasEmpty } } 复制代码
-
解包可选类型时,优先使用 guard 。
例:
// 推荐 guard let monkeyIsland = monkeyIsland else { return } bookVacation(on: monkeyIsland) bragAboutVacation(at: monkeyIsland) // 不推荐 if let monkeyIsland = monkeyIsland { bookVacation(on: monkeyIsland) bragAboutVacation(at: monkeyIsland) } if monkeyIsland == nil { return } bookVacation(on: monkeyIsland!) bragAboutVacation(at: monkeyIsland!) 复制代码
-
guard 解包时,使用相同的名称。
例:
guard let myValue = myValue else { return } 复制代码
-
当使用
if
和guard
来处理非解包时,优先采用更具有可读性的。例:
// if 这里更有可读性 if operationFailed { return } // guard 这里更有可读性 guard isSuccessful else { return } // 双重逻辑这里不具有可读性 guard !operationFailed else { return } 复制代码
-
当处理两个不同的状态时,使用 if 更好一些。
例:
// 推荐 if isFriendly { print("Hello, nice to meet you!") } else { print("You have the manners of a beggar.") } // 不推荐 guard isFriendly else { print("You have the manners of a beggar.") return } print("Hello, nice to meet you!") 复制代码
-
当处理非关联的条件时,使用 if
例:
if let monkeyIsland = monkeyIsland { bookVacation(onIsland: monkeyIsland) } if let woodchuck = woodchuck, canChuckWood(woodchuck) { woodchuck.chuckWood() } 复制代码
4.11 for-where
-
优先使用
for-where
语法。例:
// 推荐 for item in collection where item.hasProperty { // ... } // 不推荐 for item in collection { if item.hasProperty { // ... } } 复制代码
4.12 fallthrough
-
当
switch
中多条case
语句执行想通代码时,使用逗号或者范围来表示,中尽量不要使用fallthrough
。例:
// 推荐 switch value { case 1: print("one") case 2...4: print("two to four") case 5, 7: print("five or seven") default: break } // 不推荐 switch value { case 1: print("one") case 2: fallthrough case 3: fallthrough case 4: print("two to four") case 5: fallthrough case 7: print("five or seven") default: break } 复制代码
4.13 闭包
-
使用闭包调用
self
造成循环引用时。例:
myFunctionWithEscapingClosure() { [weak self] (error) -> Void in // you can do this self?.doSomething() guard let strongSelf = self else { return } strongSelf.doSomething() } 复制代码
-
声明闭包类型时,尽量不要使用括号包装。
例:
let completionBlock: (Bool) -> Void = { (success) in print("Success? \(success)") } let completionBlock: () -> Void = { print("Completed!") } let completionBlock: (() -> Void)? = nil 复制代码
-
将闭包的参数和开括号保持一行,除非太长。
4.14 枚举调用
-
调用
enum
时,尽量不要写出枚举类型。例:
// 推荐 imageView.setImageWithURL(url, type: .person) // 不推荐 imageView.setImageWithURL(url, type: AsyncImageView.Type.person) 复制代码
4.15 数组
-
尽量不要使用下标访问数组,可以使用
first
和last
等等。 -
优先使用
for item in items
语法,而不是for i in 0 ..< items.count
。可以使用for (index, value) in items.enumerated()
同时获取下标和数组。