美文网首页
Swift Code Style

Swift Code Style

作者: herbsun | 来源:发表于2017-09-20 23:09 被阅读54次

    前言

    本文档是翻译著名raywenderlich Swift Style Guide 并结合自身情况结合生成原文地址

    内容目录

    <span id = "Correctness">基本准则</span>

    将警告视为错误(健壮的工程应该没有一个警告)

    <span id = "Naming">命名</span>

    使用驼峰命名法命名类, 方法, 变量等等, 类名以及协议名首字母应大写, 然而方法名和变量名首字母小写.常量应以 k 作为首字母

    推荐 :

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

    不推荐 :

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

    通常应该避免缩略语和首字母缩略词, 缩写词和缩写应统一大写或小写. 例子:

    推荐

    let urlString: URLString
    let userID: UserID
    

    不推荐

    let uRLString: UrlString
    let userId: UserId
    

    对于函数以及 init 方法, 倾向于对所有的参数命名, 除非上下文非常简洁清晰.如果能使函数更具可读性, 我们也可以包括外部参数名称.

    func dateFromString(dateString: String) -> NSDate
    func convertPointAt(column column: Int, row: Int) -> CGPoint
    func timedAction(afterDelay delay: NSTimeInterval, perform action: SKAction) -> SKAction!
    
    // 调用方式:
    dateFromString("2014-03-14")
    convertPointAt(column: 42, row: 13)
    timedAction(afterDelay: 1.0, perform: someOtherAction)
    

    <span id = "Common">常量和变量</span>

    1.公开变量:用 Public 修饰, 如果这个变量是必传参数, 应该用 ! 标明, 如果是可选参数, 用 ? 标明
    2.私有变量:用 private 修饰.

    <span id = "Enumerations">枚举</span>

    根据Swift3苹果代码规范,枚举名称首字母应该大写,枚举值使用小驼峰. 例如:

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

    OC命名同使用大写驼峰命名法, 而枚举值必须以枚举名开头, 便于与 Swift 混编

    typedef NS_ENUM(NSUInteger, OKIShape) {
      case OKIShareRectangle
      case OKIShareSquare
      case OKIShareTriangle
      case OKIShareCircle
    }
    

    <span id = "Class-Prefixes">类前缀</span>

    按理说Swift不应该加入类前缀,但是为了和OC规范一致,在此项目类前缀为 OK + 项目名称的首字母

    <span id = "Generics">泛型</span>

    泛型类型参数应具有描述性,大写驼峰型的名字. 在类型名称时,没有一个有意义的关系或角色,请使用传统的单一的大写字母,例如T U或v

    推荐:

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

    不推荐:

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

    <span id = "Language">使用国际英语而不是美式英语</span>

    推荐:

    let color = "red"
    

    不推荐:

    let colour = "red"
    

    <span id = "Code-Organization">****代码结构****</span>

    使用扩展将代码组织成逻辑功能的块. 每个扩展应设置一个注释:// MARK: - 保持良好的注释.

    <span id = "Protocol">协议一致性</span>

    在 Swift 中, 推荐使用 extension 的方式来分割实现的代码, 在视觉上可以更好的加强用户的阅读体验, 当然, 别忘了加上 //MARK: -

    推荐:

    class MyViewcontroller: UIViewController {
      // class stuff here
    }
    
    // MARK: - 如果这个 extension 需要说明, 可以这样注释在这里
    extension MyViewcontroller: UITableViewDataSource {
      // table view data source methods
    }
    
    // MARK: - UIScrollViewDelegate
    extension MyViewcontroller: UIScrollViewDelegate {
      // scroll view delegate methods
    }
    

    不推荐:

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

    <span id = "Unused-Code">无用代码</span>

    要将文件中无用的代码和注释删除掉,这样保持界面整洁

    不推荐:

    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
    }
    
    

    推荐:

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

    <span id = "Spacing">空格</span>

    • 使用空格而不使用 Tab,永远使用4个空格来表示换行, 而不要使用 Tab 来换行.
    • 你可以设置你的 IDE 自动将输入的 Tab 转换为空格.
    if user.isHappy
    {
      // Do something
    }
    else {
      // Do something else
    }
    
    • 方法之间应该有一个空行, 以帮助在视觉清晰度和组织. 在方法内的空行应该分隔单独的功能, 但在一个方法中有太多的分隔组通常意味着你应当重构这个方法.

    • 冒号左边没有空格,右边有个一个空格.但是三目运算符(? : )和空字典([ : ])例外

    推荐:

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

    不推荐:

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

    <span id = "Word-Limit">每行的长度不应该超过120</span>

    每行的长度如果过长了话会影响阅读代码的体验, 比如有的开发者习惯左边和右边的 pannel 同时打开, 那么120个字符/行的长度是最适宜的, 这样可以保证在开关左右 pannel 的时候 IDE 不会出现换行, 你可以在 Xcode 中设置每行的最大长度的警告线 (Preferences > Text Editing > Page guide at column: 120)

    <span id = "Line">不要使用 C 语言的括号形式 空格和换行</span>

    条件控制语句, 例如 if/else/while/switch 等的左括号应当在本行, 而右括号应当在新的一行.

    推荐:

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

    不推荐:

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

    如果方法名过长需要换行了话, 在第二行空4格

    当一个函数超过120的时候, 可以在适当的地方开始换行(如某个参数的结尾)

    推荐:

    func reticulateSplines(spline: [Double], adjustmentFactor: Double,
        translateConstant: Int, comment: String) -> Bool {
      // reticulate code goes here
    }
    

    <span id = "Comments">注释</span>

    首先, 虽然注释很重要,但是最好的注释就是自文档化.注释是用来解释说明某段特定的代码做了些什么, 或者为什么这么做.同时,注释必须能否保持更新或删除.例外:这并不适用于那些用来生成文档的注释.

    <span id = "Declaration-Comments">类的声明注释</span>

    每一个class、category或protocol都需要一个注释说明其作用及如何使用.
    注释要包括概述、必要参数、版本信息、作者、更新信息, 如:

    /// 说明该类的作用, 及如何使用
    ///
    /// 参数: 必要的参数
    ///
    /// @since 版本信息
    /// @author 更新作者 更新信息
    

    另外, 如果是public的方法, 要对每个方法做注释, 包括参数、返回值以及注意情况等

    实现中的注释

    用竖线`` 代替引号“”, 这样能消除一些歧义, 如:

    // Sometimes we need `count` to be less than zero.
    或者本身带有引号的情况, 如:

    // Remember to call `StringWithoutSpaces("foo bar baz")`

    不要在注释中嵌入代码片段

    不要在注释中嵌入代码片段, 因为代码要尽可能的自文档化, 并且不利于自动生成文档

    <span id = "Function-Comments">对方法的描述以及引用</span>

    当涉及方法并包括所需的参数名称时, 应该从调用者的角度命名, 或使用_命名为缺省.

    从你自己创建的init方法调用调用 convertPointAt(column:row:) .
    
    如果你调用dateFromString(_:)要确定你提供了一个格式为"yyyy-MM-dd"的字符串.
    
    如果你从 viewDidLoad()调用方法 timedAction(afterDelay:perform:) 记得提供一个调整延迟值和一个动作来执行此方法.
    
    你不应该直接调用数据源方法: tableView(_:cellForRowAtIndexPath:) .
    

    如果有疑问, 可以参照 Xcode 在跳转栏的方法, 风格和它保持一致.

    <span id = "Class-Struct">类和结构体</span>

    <span id = "Instruct">何时选用 Class, 何时选用 Struct?</span>

    在 Swift 中, Class 是引用类型, Struct 是值类型, 值类型有许多好处, 例如当你传递一个值类型给另一个对象是, 发生的是深拷贝(deep copy)操作, 不会出现改了后者就改了前者的问题, 还意味着线程安全, 虽然很有可能有人会说值类型传递是相对耗时的(的确, 相对引用类型, 传递值类型是会稍微耗时一点), 但是, 我们应当认为在如今的世界中, 这种不是复杂度提升级别的耗时(耗时为一个常量)相对于值类型能带来的好处是非常明显的, 所以我们建议在 Swift 中请优先考虑使用值类型. (关于性能问题如果你深陷网络"性能陷阱"不能自拔, 可以查阅 WWDC 2015 - Swift Value Type 来看看苹果官方的回答, 更可以遵循我组第二原则 : 没有真正看到性能问题时不要考虑性能问题), 下面我们就创建对象时的选用 Class 的情况做一个简单的描述:

    1.当一个对象具有唯一性的时候选用 Class:
    如果你要创建的对象是一个具有 identifer 的对象, 例如 User, Task, 哪怕两个 User 具有相同的名字, 但是不同的 UID 即表明他们不是同一个人, 那么这个时候 Class 更加合适, 但是比如这个 User.Birthday 可能是一个 Date(你自定义的 Date), 这个 Date 就更适合用 Struct, 因为两个用户如果都出生在10月1日, 那么这个10月1日是对等的.

    2.不要和系统的 API 对着干:
    如果系统的 API 要你传一个 Class, 那么就传一个 Class, 不要想方设法的对抗系统(尤其是使用只有你看得懂的黑魔法).

    除非语法需要, 否则不要显式使用 self

    在 Swift 中, 默认调用方法或者 property 的时候不需要使用 self.xxx 来调用, 另外, Swift 要求闭包(Closure)中使用 self.xxx 来显式的暗示 self 已经被 retain 了, 所以, 在正常的方法调用以及 property 的调用中, 不要使用 self.xxx 来调用以免产生歧义.

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

    <span id = "Computed-Property">计算型属性 (Computed Property)</span>

    如果一个 Computed Property 是只读的, 则不要写 get:

    推荐:

    var diameter: Double {
      return radius * 2
    }
    

    不推荐:

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

    <span id = "Final">Final</span>

    当自定义类不允许被继承时,应该用final标记这个类.例如:

    // Box这个类不允许被继承
    final class Box<T> {
      let value: T 
      init(_ value: T) {
        self.value = value
      }
    }
    

    <span id = "Closures">闭包(Closures)</span>

    尽可能使用尾随闭包语法. 在所有情况下, 闭包的参数需要有一个描述性的名称.

    闭包中要注意循环引用情况,使用[weak self]解决此问题

    推荐:

    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
      }) { f in
        self.myView.removeFromSuperview()
    }
    

    对于只有一行表达式的闭包,如果上下文清晰的话可以隐式返回

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

    <span id = "Types">类型 (Types)</span>

    尽量使用 Swift 的原生类型, Swift 可以通过桥接文件对 OC 进行调用. 对一个属性进行声明时, 倾向于同时声明属性的类型.

    推荐:

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

    不推荐:

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

    在Sprite Kit代码中, 使用CGFloat, 如果它使代码更简洁, 避免过多的转换

    <span id = "Constants">常量 (Constants)</span>

    使用 let 定义常量, var 定义变量. 如果一个变量不会发生变化, 尽量用 let 修饰而不是 var.
    对于 Class 应当都使用 let 而不是 var, 因为 Class 是引用类型, var 和 let 的修饰符并不可以阻止其属性被修改

    提示:一个好的技巧是用 let 定义一切属性, 只有当编译器提示时才改为 var.

    <span id = "Type-Inference">类型推断 (Type Inference)</span>

    虽然通过编译器的类型推断使代码更加紧凑, 但是, 这意味着要求你的变量声明的可读性要比之前更高.
    所以, 尽量给常量或变量声明带上确切的类型.

    推荐:

    let maximumWidth: CGFloat = 106.5
    

    不推荐:

    let maximumWidth = 106.5
    

    <span id = "Optionals">可选类型 (Optionals)</span>

    • 在nil值可能出现的情况下, 将变量跟函数返回值的类型通过?定义成Optional.
    • 只有在确定实例变量会在初始化之后才被使用的情况下, 通过 ! 将其定义为隐式解包类型(Implicitly Unwrapped Types),
    • 比如说会在viewDidLoad中被创建的子视图. 在访问一个Optional值时, 如果该值只被访问一次, 或者之后需要连续访问多个Optional值, 请使用链式Optional语法:
    self.textContainer?.textLabel?.setNeedsDisplay()
    

    if let 判断可选类型一次, 可以在作用域内做多个操作

    if let textContainer = self.textContainer {
      // do many things with textContainer
    }
    
    • 当命名可选变量或属性, 避免命名为比如:optionalString 或者 maybeView,因为他们的可选性已经在类型声明时明确了.
    • if let 判断语句尽量使解包前后变量名一致.

    推荐:

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

    不推荐:

    var optionalSubview: UIView?
    var volume: Double?
    
    if let unwrappedSubview = optionalSubview {
      if let realVolume = volume {
        // do something with unwrappedSubview and realVolume
      }
    }
    
    • 不要使用强转符号(!)将一个 nullable 的对象转换为 nonnull 的对象, 绝大多数的崩溃错误都是由此引起的

    • 相对于if let 我们更偏好使用 guard let, 因为 guard 语法会强制你实现 else 的逻辑, 让错误的 handle 更加优雅

    <span id = "Struct-Initializers">初始化结构体 (Struct Initializers)</span>

    尽量使用新的 Swift 的 API 而不是 Objective-C 的 bridge 后的 API:

    推荐:

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

    不推荐:

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

    推荐使用 Swift 形式的常量: Module.Constant, 而不是 C 形式的常量: ModuleConstant:

    推荐:

    CGRect.infinite, CGRect.null
    

    不推荐:

    CGRectInfinite, CGRectNull
    

    <span id = "Lazy-Initialization">懒加载 (Lazy Initialization)</span>

    使用延迟初始化的精细控制对象的生存期.对于延迟加载视图的视图控制器

    style1.
    private lazy var locationManager: CLLocationManager = CLLocationManager()
    
    style2.
    private lazy var locationManager: CLLocationManager = {
     let locationManager = CLLocationManager()
     return locationManager
    }()
    
    style3.
    lazy var locationManager: CLLocationManager = self.makeLocationManager()
    
    private func makeLocationManager() -> CLLocationManager {
      let manager = CLLocationManager()
      manager.desiredAccuracy = kCLLocationAccuracyBest
      manager.delegate = self
      manager.requestAlwaysAuthorization()
      return manager
    }
    

    空数组和字典的类型批注 (Type Annotation for Empty Arrays and Dictionaries)

    空数组和字典, 使用类型批注. 对指定的数组或字典, 多行文本, 请使用类型批注.

    推荐:

    var names: [String] = []
    var lookup: [String: Int] = [:]
    

    不推荐:

    var names = [String]()
    var lookup = [String: Int]()
    

    <span id = "Syntactic-Sugar">语法糖 (Syntactic Sugar)</span>

    使用类型定义个快捷语法而不要使用完整的语法

    推荐:

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

    不推荐:

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

    <span id = "Memory-Management">内存管理 (Memory Management)</span>

    推荐:

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

    不推荐:

    // 如果self在response返回之前就被释放了 可能会引发崩溃
    resource.request().onComplete { [unowned self] response in
      let model = self.updateModel(response)
      self.updateUI(model)
    }
    

    不推荐:

    resource.request().onComplete { [weak self] response in
      let model = self?.updateModel(response)
      self?.updateUI(model)
    }
    

    <span id = "Access-Control">流程控制 (Access Control)</span>

    <span id = "Control-Flow">控制流 (Control Flow)</span>

    遍历请使用for-in的格式, 而不是C语言风格的for-condition-increment的格式

    推荐:

    for _ in 0..<3 {
      print("Hello three times")
    }
    
    for (index, person) in attendeeList.enumerate() {
      print("\(person) is at position #\(index)")
    }
    
    for index in 0.stride(to: items.count, by: 2) {
      print(index)
    }
    
    for index in (0...3).reverse() {
      print(index)
    }
    

    不推荐:

    var i = 0
    while i < 3 {
      print("Hello three times")
      i += 1
    }
    
    var i = 0
    while i < attendeeList.count {
      let person = attendeeList[i]
      print("\(person) is at position #\(i)")
      i += 1
    }
    

    <span id = "Golden-Path">Golden Path</span>

    善用guard let 而不是嵌套if let 来做多个条件判断

    推荐:

    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
    }
    

    不推荐:

    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
      }
    }
    

    当多个可选类型需要解包.应该使用guard let来减少嵌套.例如:

    推荐:

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

    不推荐:

    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")
    }
    

    <span id = "Semicolons">分号 (Semicolons)</span>

    • Swift在任何语句后都不需要分号, 分号仅在你将多个语句写在一行时有用(用于区分语句)
    • 不要将多个语句写在一行然后用分号区分
      这个规则的唯一例外是for语句, 它需要分号. 然而, 在可能的情况下尽量使用可选的 for-in 语句

    推荐:

    let swift = "not a scripting language"
    

    不推荐:

    let swift = "not a scripting language";
    

    ## <span id = "Parentheses">****圆括号**** (Parentheses)</span>

    不需要括号条件, 应予以删除.

    推荐:

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

    不推荐:

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

    总结

    无规矩不成方圆,愿这篇文章能够帮助到大家。本人会不定时更新,如有异议,请issue我。

    相关文章

      网友评论

          本文标题:Swift Code Style

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