Swift style guide

作者: 小山Sam | 来源:发表于2016-10-25 00:22 被阅读0次

    Introduction

    之前有一个Objective-C style guide.,这一篇是针对swift的一个补充。

    Credits

    API Design Guidelines是苹果专门针对API的一个规范,本规范涉及到一些相关的内容,大都是保持和苹果的规范一致。

    它绝大部分内容,来自The Official raywenderlich.com Swift Style Guide.,并加入了自己的一些偏好。

    同时,Swift语言在快速的发展中,这个规范也会随着Swift的发展、以及我们对Swift更多的使用和了解,持续地进行修改和完善。

    Table of Contents

    <h2 id="naming"> Naming </h2>

    classes, structures, enumerations 和 protocols采用首字母写的驼峰命名法,method names and variables采用首字母写的驼峰命名法。

    Preferred:

    private let maximumWidgetCount = 100
    
    class WidgetContainer {
      var widgetButton: UIButton
      let widgetHeightPercentage = 0.85
    }
    

    Not Preferred:

    let MAX_WIDGET_COUNT = 100
    
    class app_widgetContainer {
      var wBut: UIButton
      let wHeightPct = 0.85
    }
    

    缩写应该避免,但如URL、ID这种常见的缩写可以使用。

    API Design Guidelines中提到,如果使用这些缩写,字母应该全为大写或者小写。Examples:

    Preferred

    let urlString: URLString
    let userID: UserID
    

    Not Preferred

    let uRLString: UrlString
    let userId: UserId
    

    使用argument label,替代注释,使代码self-documenting,可读性更强。

    func convertPointAt(column: Int, row: Int) -> CGPoint
    func timedAction(afterDelay delay: NSTimeInterval, perform action: SKAction) -> SKAction!
    
    // would be called like this:
    convertPointAt(column: 42, row: 13)
    timedAction(afterDelay: 1.0, perform: someOtherAction)
    

    Protocols

    按照苹果的API Design Guidelines,Protocols名字可以使用名词来描述这个Protocols的内容,比如Collection, WidgetFactory

    或以-ing、-able结尾来描述Protocols实现的一些功能,比如: Equatable, Resizing

    Enumerations

    按照苹果的API Design Guidelines,枚举值使用小写开头的驼峰命名法,也就是lowerCamelCase。

    enum Shape {
      case rectangle
      case square
      case rightTriangle
      case equilateralTriangle
    }
    

    Class Prefixes

    在Swift里面,每一个module都是一个namesapce。而在ObjC里没有namespace的概念,只是在每个类名前面添加前缀,比如NSArray。

    当不同的module有同名的类名时,需要指明module name。

    import SomeModule
    
    let myClass = MyModule.UsefulClass()
    

    Generics

    范型的类型名,应该是描述性的名词、upperCamelCase。如果不能起一个有意义的关系或角色名称,可以使用TUV

    Preferred:

    struct Stack<Element> { ... }
    func writeTo<Target: OutputStream>(inout target: Target)
    func max<T: Comparable>(x: T, _ y: T) -> T
    

    Not Preferred:

    struct Stack<T> { ... }
    func writeTo<target: OutputStream>(inout t: target)
    func max<Thing: Comparable>(x: Thing, _ y: Thing) -> Thing
    

    Language

    使用美国英语。

    Preferred:

    let color = "red"
    

    Not Preferred:

    let colour = "red"
    

    Code Organization

    多使用extensions来组织代码。每个 extensions使用// MARK: -来分隔开。

    Protocol Conformance

    当一个类遵守一个协议时,使用extensions来组织代码。

    Preferred:

    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
    }
    

    Not Preferred:

    class MyViewcontroller: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
      // all methods
    }
    

    Unused Code

    能删除的代码,都删掉。

    Not Preferred:

    override func didReceiveMemoryWarning() {
       super.didReceiveMemoryWarning()
      // Dispose of any resources that can be recreated.
    }
    
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
       // #warning Incomplete implementation, return the number of sections
       return 1
    }
    
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
      // #warning Incomplete implementation, return the number of rows
      return Database.contacts.count
    }
    
    

    Preferred:

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
      return Database.contacts.count
    }
    

    Minimal Imports

    尽可能减少依赖和imports。

    Spacing

    • 缩进使用2个空格:
    • 方法体的花括号需要在新的一行开启,在新的一行关闭。而其它花括号(if/else/switch/while etc.),加入一个空格后在行尾开启,在新一行关闭(Xcode默认)。
    • 提示:⌘A选中代码后使用Control-I (或者菜单Editor\Structure\Re-Indent)来调整缩进.

    Preferred:

    if user.isHappy {
      // Do something
    } else {
      // Do something else
    }
    

    Not Preferred:

    if user.isHappy
    {
      // Do something
    }
    else {
      // Do something else
    }
    
    • methods之间只留一个空行。methods内部,使用空行来分隔不同功能的代码,为不同的section。一个method内部的section不宜太多,否则应该考虑分拆。
    • 冒号左边没有空格,右边有一个空格。Exception:? :[:]

    Preferred:

    class TestDatabase: Database {
      var data: [String: CGFloat] = ["A": 1.2, "B": 3.2]
    }
    

    Not Preferred:

    class TestDatabase : Database {
      var data :[String:CGFloat] = ["A" : 1.2, "B":3.2]
    }
    

    Comments

    尽可能避免大量使用注释,好的代码应该尽可能是self-documenting。

    如果需要注释,它只用来解释为什么这段代码要这么写,而不是解释代码的逻辑。
    并且是正确的,代码变化时也需要马上更新,不能有误导。

    Exception: 上面两条不适用于生成文档用的注释.

    Classes and Structures

    Which one to use?

    Structs是value semantics

    而Classes是reference semantics.

    有些时候,只需要Structs就够了。但有些Class,由于历史原因,被弄成类,如NSDateNSSet等。

    Example definition

    下面是一个比较规范的Class定义:

    class Circle: Shape {
      var x: Int, y: Int
      var radius: Double
      var diameter: Double {
        get {
          return radius * 2
        }
        set {
          radius = newValue / 2
        }
      }
    
      init(x: Int, y: Int, radius: Double) {
        self.x = x
        self.y = y
        self.radius = radius
      }
    
      convenience init(x: Int, y: Int, diameter: Double) {
        self.init(x: x, y: y, radius: diameter / 2)
      }
    
      func describe() -> String {
        return "I am a circle at \(centerString()) with an area of \(computeArea())"
      }
    
      override func computeArea() -> Double {
        return M_PI * radius * radius
      }
    
      private func centerString() -> String {
        return "(\(x),\(y))"
      }
    }
    

    上面的例子,给我们演示了这些规范:

    • 冒号在用于指明类型时,左边没有空格,右边有一个空格。
    • 当一组变量、常量有关联时,定义在一行里。
    • 函数修饰符internal是缺省值,可以省略。重载一个函数时,访问修饰符也可以省略掉。

    Use of Self

    避免使用self来访问属性。除非需要区分函数参数和属性。

    class BoardLocation {
      let row: Int, column: Int
    
      init(row: Int, column: Int) {
        self.row = row
        self.column = column
        
        let closure = {
          print(self.row)
        }
      }
    }
    

    Computed Properties

    Computed property一般是只读,同时省略get clause。get clause只是当set clause存在时才需要写。

    Preferred:

    var diameter: Double {
      return radius * 2
    }
    

    Not Preferred:

    var diameter: Double {
      get {
        return radius * 2
      }
    }
    

    Final

    当一个类不想被继承时,使用final关键字。Example:

    // Turn any generic type into a reference type using this Box class.
    final class Box<T> {
      let value: T 
      init(_ value: T) {
        self.value = value
      }
    }
    

    Closure Expressions

    方法的参数列表最后一参数类型为闭包时,可以使用尾闭包。但只在只存在一个闭包参数时才使用尾闭包。

    Preferred:

    UIView.animateWithDuration(1.0) {
      self.myView.alpha = 0
    }
    
    UIView.animateWithDuration(1.0,
      animations: {
        self.myView.alpha = 0
      },
      completion: { finished in
        self.myView.removeFromSuperview()
      }
    )
    

    Not Preferred:

    UIView.animateWithDuration(1.0, animations: {
      self.myView.alpha = 0
    })
    
    UIView.animateWithDuration(1.0,
      animations: {
        self.myView.alpha = 0
      }) { f in
        self.myView.removeFromSuperview()
    }
    

    只有一个表达式的、用来返回值的闭包,可以省略return。

    attendeeList.sort { a, b in
      a > b
    }
    

    Types

    尽可能使用Swift原生类型,而不是使用ObjC的NS类型。

    Preferred:

    let width = 120.0                                    // Double
    let widthString = (width as NSNumber).stringValue    // String
    

    Not Preferred:

    let width: NSNumber = 120.0                          // NSNumber
    let widthString: NSString = width.stringValue        // NSString
    

    但是有些情况例外,比如在写Sprite Kit代码时,用CGFloat可以减少转换。

    Constants

    尽可能使用let,只有在需要使用变量的时候使用var。

    Tip: 有一个办法能达到上面的目的,就是自己只写let,让编译器帮你确定哪些需要改成var。

    使用static let定义类常量,而不是实例常量,或者全局常量。

    Preferred:

    enum Math {
      static let e  = 2.718281828459045235360287
      static let pi = 3.141592653589793238462643
    }
    
    radius * Math.pi * 2 // circumference
    
    

    Note: 使用枚举定义常量的好处是让常量定义在特定的命名空间。

    Not Preferred:

    let e  = 2.718281828459045235360287  // pollutes global namespace
    let pi = 3.141592653589793238462643
    
    radius * pi * 2 // is pi instance data or a global constant?
    

    Optionals

    当访问一个optional value前,需要访问多个optional value时,使用optional chaining:

    self.textContainer?.textLabel?.setNeedsDisplay()
    

    访问一个optional value后,需要执行多个操作,可以使用optional binding:

    if let textContainer = self.textContainer {
      // do many things with textContainer
    }
    

    给optional变量命名时,不要使用类似optionalStringmaybeView这样的方式,因为optional-ness已经在类型声明中体现。

    相对应的,使用unwrapped value时,避免使用unwrappedViewactualLabel`,使用optional变量名就可以了。

    Preferred:

    var subview: UIView?
    var volume: Double?
    
    // later on...
    if let subview = subview, volume = volume {
      // do something with unwrapped subview and volume
    }
    

    Not Preferred:

    var optionalSubview: UIView?
    var volume: Double?
    
    if let unwrappedSubview = optionalSubview {
      if let realVolume = volume {
        // do something with unwrappedSubview and realVolume
      }
    }
    

    Struct Initializers

    使用Swfit原生的struct initializers,而不是遗留的CGGeometry constructors。

    Preferred:

    let bounds = CGRect(x: 40, y: 20, width: 120, height: 80)
    let centerPoint = CGPoint(x: 96, y: 42)
    

    Not Preferred:

    let bounds = CGRectMake(40, 20, 120, 80)
    let centerPoint = CGPointMake(96, 42)
    

    同样的,使用struct-scope constants CGRect.infiniteCGRect.null, etc, 而不是global constants CGRectInfinite, CGRectNull, etc。

    Type Inference

    对于适用于类型推断的地方,使用类型推断。

    Preferred:

    let message = "Click the button"
    let currentBounds = computeViewBounds()
    var names = ["Mic", "Sam", "Christine"]
    let maximumWidth: CGFloat = 106.5
    

    Not Preferred:

    let message: String = "Click the button"
    let currentBounds: CGRect = computeViewBounds()
    let names = [String]()
    

    Syntactic Sugar

    尽可能使用语法糖。

    Preferred:

    var deviceModels: [String]
    var employees: [Int: String]
    var faxNumber: Int?
    

    Not Preferred:

    var deviceModels: Array<String>
    var employees: Dictionary<Int, String>
    var faxNumber: Optional<Int>
    

    Functions vs Methods

    不依附于任何class或type的函数被称为free function,应该尽量避免使用,因为不太好找到这个方法。

    Preferred

    let sorted = items.mergeSort()  // easily discoverable
    rocket.launch()  // clearly acts on the model
    

    Not Preferred

    let sorted = mergeSort(items)  // hard to discover
    launch(&rocket)
    

    Free Function Exceptions

    let tuples = zip(a, b)  // feels natural as a free function (symmetry)
    let value = max(x,y,z)  // another free function that feels natural
    

    Memory Management

    用下面prefered的方式,避免循环引用。

    Preferred

    resource.request().onComplete { [weak self] response in
      guard let strongSelf = self else { return }
      let model = strongSelf.updateModel(response)
      strongSelf.updateUI(model)
    }
    

    Not Preferred

    // might crash if self is released before response returns
    resource.request().onComplete { [unowned self] response in
      let model = self.updateModel(response)
      self.updateUI(model)
    }
    

    Not Preferred

    // deallocate could happen between updating the model and updating UI
    resource.request().onComplete { [weak self] response in
      let model = self?.updateModel(response)
      self?.updateUI(model)
    }
    

    Access Control

    访问修饰符应该放在靠前的位置,前面只能有static@IBAction@IBOutlet

    Preferred:

    class TimeMachine {  
      private dynamic lazy var fluxCapacitor = FluxCapacitor()
    }
    

    Not Preferred:

    class TimeMachine {  
      lazy dynamic private var fluxCapacitor = FluxCapacitor()
    }
    

    Golden Path

    嵌套的if会让代码的缩进层次不齐(整齐的缩进被称作Golden Path),会让代码可读性变差,使用guard来做函数输入合法性检查,可以减少if嵌套。

    Preferred:

    func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
    
      guard let context = context else { throw FFTError.noContext }
      guard let inputData = inputData else { throw FFTError.noInputData }
        
      // use context and input to compute the frequencies
        
      return frequencies
    }
    

    Not Preferred:

    func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
    
      if let context = context {
        if let inputData = inputData {
          // use context and input to compute the frequencies
    
          return frequencies
        }
        else {
          throw FFTError.noInputData
        }
      }
      else {
        throw FFTError.noContext
      }
    }
    

    Preferred:

    guard let number1 = number1, number2 = number2, number3 = number3 else { fatalError("impossible") }
    // do something with numbers
    

    Not Preferred:

    if let number1 = number1 {
      if let number2 = number2 {
        if let number3 = number3 {
          // do something with numbers
        }
        else {
          fatalError("impossible")
        }
      }
      else {
        fatalError("impossible")
      }
    }
    else {
      fatalError("impossible")
    }
    

    Failing Guards

    Guard检查失败执行的语句应该退出当前方法,并且应该只有一条语句,如return, throw, break, continue, and fatalError()。如果需要多行语句,考虑使用defer

    Parentheses

    保住条件语句的圆括号应该省略。

    Preferred:

    if name == "Hello" {
      print("World")
    }
    

    Not Preferred:

    if (name == "Hello") {
      print("World")
    }
    

    <h2 id="other-swift-style-guides">Other Swift Style Guides</h2>

    上面的规范可能你不太喜欢,或者没有涉及到你需要的某些方面,可以参考下面的内容:

    相关文章

      网友评论

        本文标题:Swift style guide

        本文链接:https://www.haomeiwen.com/subject/zrqruttx.html