我的 Swift Style

981 阅读17分钟

v 1.0 版本

严重参考

Google Swift Style Guide

Linkedin Swift Style Guide

Apple API Design Guidelines

一、代码结构

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 语句中的间隔

用一个空格分隔

  • 如果条件语句或选择语句(如 ifguardWhenSwitch)等等以圆括号 ( 开头,则在该语句的关键字与其后面的表达式之间空一格。

    例:

    // 推荐
    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 形式。

  • 一些缩略词命名在开始时,全部小写,如果在中间,全部大写。例如 URLJSONHTML 等等。

    例:

    // 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 {   
            // ...
        }
    }
    
  • 当使用类属性作为单例使用时,应该用达成共识的 shareddefault 等来命名。

2.8 全局常量

  • 全局变量采用 lowerCamelCase 样式,并不使用类似 kg 等等前缀。

    例:

    // 推荐
    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, structsclasses 和某个类相关时,可以将它嵌套进这个类。

    例:

     // 推荐
    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
    }
    
  • 当使用 ifguard 来处理非解包时,优先采用更具有可读性的。

    例:

    // 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 数组

  • 尽量不要使用下标访问数组,可以使用 firstlast 等等。

  • 优先使用 for item in items 语法,而不是 for i in 0 ..< items.count。可以使用 for (index, value) in items.enumerated() 同时获取下标和数组。