Swift设计指南

这是一个Swift开发代码规范git

正确性

尽量地让你的代码在没有警告的状况下编译。这个规则适用不少的状况,好比#selector类型去替代适用字符串字面值。github

命名

命名遵循如下关键点:swift

  • 尽量的明确使用场景
  • 清晰比简短更重要
  • 使用驼峰命名法
  • 类型和协议使用大写字母开头,其余的小写字母开头
  • 包含全部你须要的关键字,省略没必要要的关键字
  • 用使用角色来命名,而不是类型
  • 弱引用类型要作注释说明
  • 要善用控制流
  • 工厂方法用make开头
  • 方法命名遵循如下规则
    • 动做化的方法遵循-ed,-ing这样的规则
    • 名词化的方法遵循相似formX这样的规则就能够了
    • boolean类型的方法,命名出来是要给人看到是用来作判断用的
    • protocols的描述应该名词化
    • protocols若是是描述一个“可否......”的时候,应该以-able或者ible结束
  • 使用专业的术语
  • 避免缩写
  • 尽量经过方法 和属性来实现功能。
  • 命名要具备约束力
  • 上下文的首字母缩写要保持一致性
  • 避免函数返回类型重载
  • 选择好的参数命名看成文档使用
  • 闭包和元组的参数要写标识
  • 要善用默认参数

Xcode的方法属性简介列表

(img) 当你在这个列表里参阅的时候,清晰的含义就显得格外重要了。你须要让别人尽量简单的去参阅一个方法名。数组

  1. 编写没有参数的方法。好比:addTarget
  2. 编写方法的时候要写参数的标签。

Class的前缀

Swift里面的类型已经自动地增长了它们所属模块的命名空间,你不须要为它们编写前缀。若是来自不一样模块的两个名称发生冲突,您能够经过使用模块名称预先肯定类型名称来消除歧义。可是,只有在可能出现混淆的状况下才指定模块名称。(可是在tdw项目里面,仍是建议使用前缀,这个前缀是用来做为tdw出品的标识)xcode

import SomeModule                               
let myClass = MyModule.UsefulClass()
复制代码

代理

当你自定义一个代理方法等时候,第一个参数应该是一个匿名参数,这个参数是代理源。(UIkit包含了不少这样的例子) 正确的:安全

func namePickerView(_ namePickerView: NamePickerView, didSelectName name: String)
func namePickerViewShouldReload(_ namePickerView: NamePickerView) -> Bool
复制代码

错误的:bash

func namePickerView(_ namePickerView: NamePickerView, didSelectName name: String)
func namePickerViewShouldReload(_ namePickerView: NamePickerView) -> Bool
复制代码

使用上下文类型推断

利用编译器上下文类型推断编写更短更简洁的代码。闭包

正确的:app

let selector = #selector(viewDidLoad)
view.backgroundColor = .red
let toView = context.view(forKey: .to)
let view = UIView(frame: .zero)
复制代码

错误的ide

let selector = #selector(ViewController.viewDidLoad)
view.backgroundColor = UIColor.red
let toView = context.view(forKey: UITransitionContextViewKey.to)
let view = UIView(frame: CGRect.zero)
复制代码

模版

范型类型应该被有意义的描述,用驼峰命名首字母大写的方式编写。若是你找不到词语用来表达这个类型的意思,可使用传统的26个字母中的某一个,大写,来表示,好比:T,U,V等。 正确的:

struct Stack<Element> { ... }
func write<Target: OutputStream>(to target: inout Target)
func swap<T>(_ a: inout T, _ b: inout T)
复制代码

错误的:

struct Stack<T> { ... }
func write<target: OutputStream>(to target: inout target)
func swap<Thing>(_ a: inout Thing, _ b: inout Thing)
复制代码

语言

使用美式英语来描述API 正确的:

let color = "red"
复制代码

错误的:

let colour = "red"
复制代码

代码组织

使用extensions来组织你的代码逻辑块。每一个extension都应该使用// MARK: -来讲明该extension的功能。

协议的遵循

特别的,当你增长一个继承协议的时候,最好用extension分割协议方法。

正确的:

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
}
复制代码

无用的代码

没有用的代码,该删掉的仍是要删掉,不要只是注释掉

间隔

xcode设置:

控制流

正确的:

if user.isHappy {
  // Do something
} else {
  // Do something else
}
复制代码

错误的:

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]
}
复制代码

注释

当须要时,使用注释来解释为何特定的代码会作一些事情。注释必须保持最新或删除。

类和结构体

该使用哪一个?

记住,struct具备值语义。对于没有身份的事物使用结构。包含a、b、c的数组与包含a、b、c的另外一个数组彻底相同,它们是彻底可互换的。无论你用的是第一个数组仍是第二个数组,由于它们表示的是彻底同样的东西。这就是为何数组是结构的缘由。

类具备引用语义。为具备标识或特定生命周期的事物使用类。你能够将一我的建模为一个类,由于两个对象是两个不一样的东西。仅仅由于两我的有相同的名字和生日,并不意味着他们是同一我的。可是这我的的生日是一个结构体,由于1950年3月3日的日期和1950年3月3日的任何其余日期对象是同样的。日期自己没有标识。

定义的例子

下面是一个精心设计的类定义示例:

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

  override func area() -> Double {
    return Double.pi * radius * radius
  }
}

extension Circle: CustomStringConvertible {
  var description: String {
    return "center = \(centerString) area = \(area())"
  }
  private var centerString: String {
    return "(\(x),\(y))"
  }
}
复制代码

上面的例子遵循如下的设计指南:

  • 属性、变量、常量、参数声明和其余在冒号以后(不在以前)有空格的语句的指定类型,例如x: Int和Circle: Shape。
  • 若是共享一个共同目的/上下文,则在一行中定义多个变量和结构。
  • 缩进getter和setter定义和属性观察者。
  • 不要添加修饰符,例如internal当它们已是默认值时。一样,在覆盖方法时,不要重复使用修饰符。
  • 在扩展中添加额外的功能 (e.g. printing) 。
  • 隐藏非共享、实现细节如 centerString 扩展内部使用 private 访问控制的代码。

Self的使用

为了简洁起见,当不须要访问对象的属性或调用它的方法时要避免使用‘self’。

只有当编译器要求必须使用self的时候才使用。

计算属性:

为了简洁起见,若是一个计算属性是只读的,则省略get子句。只有在提供了set子句时,才须要get子句。

正确的:

var diameter: Double {
    return radius * 2
}
复制代码

错误的:

var diameter: Double {
    get {
        return radius * 2
    }
}
复制代码

Final

不容许对其修饰的内容进行继承或者从新操做。
final标记class或members可能会分散主题,这没必要需。然而,使用 final 有时能够阐明你的意图,是值得的。在如下示例中, Box 有一个特殊用途而且并不打算容许在派生类定制。标记 final 会让这很清楚。

// 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
    }
}
复制代码

函数声明

在一行上使用简洁的功能声明(包括大括号)

func reticulateSplines(spline: [Double]) -> Bool {
   // reticulate code goes here
}
对于具备长签名的功能, 在适当的点添加换行符,并后续行上添加一个额外的缩进
func reticulateSplines(spline: [Double], adjustmentFactor: Double,
        translateConstant: Int, comment: String) -> Bool {
     // reticulate code goes here
}
复制代码

闭包表达式

仅在参数列表末尾有单个闭包表达式参数时才使用尾随闭包。并给出闭包参数的描述性名称。

正确的:

UIView.animate(withDuration: 1.0) {
  self.myView.alpha = 0
}

UIView.animate(withDuration: 1.0, animations: {
  self.myView.alpha = 0
}, completion: { finished in
  self.myView.removeFromSuperview()
})
复制代码

错误的:

UIView.animate(withDuration: 1.0, animations: {
  self.myView.alpha = 0
})

UIView.animate(withDuration: 1.0, animations: {
  self.myView.alpha = 0
}) { f in
  self.myView.removeFromSuperview()
}
复制代码

对于上下文清晰的简单闭包表达式,能够用隐式返回:

attendeeList.sort { a, b in
  a > b
}
复制代码

使用尾随闭包的连接方法在上下文中应该清晰易读。关于间距,换行,何时使用命名和匿名的参数,这些都留给做者自行判断。

let value = numbers.map { $0 * 2 }.filter { $0 % 3 == 0 }.index(of: 90)

let value = numbers
  .map {$0 * 2}
  .filter {$0 > 50}
  .map {$0 + 10}
复制代码

类型

尽量使用Swift自带的类型。Swift为Objective-C提供了桥接文件,当你须要时你仍然可使用它全部的方法。

正确的:

let width = 120.0                                    // Double
let widthString = (width as NSNumber).stringValue    // String
复制代码

错误的:

let width: NSNumber = 120.0                          // NSNumber
let widthString: NSString = width.stringValue        // NSString
复制代码

常量

定义常量使用 let 关键字, 变量使用 var 关键字。若是变量的值不会改变一般使用 let,而不是 var 。

提示:有一个好方法就是全部东西都使用 let 定义,编译器报警告时再把它改成 var

您能够在类型上定义常量,而不是使用类型属性定义该类型的实例 。使用 static let 声明一个仅仅做为常数的类型属性。用这种方式声明的类型属性一般优于全局常量,由于他们更容易从实例属性中区分。
正确的:

enum Math {
  static let e = 2.718281828459045235360287
  static let root2 = 1.41421356237309504880168872
}

let hypotenuse = side * Math.root2
注解:使用这种枚举的优势是它不会被不当心实例化或者只是一个单纯的命名空间
复制代码

错误的:

let e = 2.718281828459045235360287  // pollutes global namespace
let root2 = 1.41421356237309504880168872

let hypotenuse = side * root2 // what is root2?
复制代码

静态方法和变量类型属性

静态方法和类型属性的工做方式相似于全局函数和全局变量,应该谨慎使用。当功能局限于某一特定类型或者要求与Objective-C互操做时是颇有用的。

可选类型

将变量和函数返回类型声明为可选类型,返回一个nil值是可接受的。

只有在实例变量使用前肯定会初始化的前提下,才能使用 !隐式强解的类型声明,例如将设置的子视图viewDidLoad。

访问可选值时,若是该值仅访问一次,或者链中有多个可选项,请使用可选连接:

self.textContainer?.textLabel?.setNeedsDisplay()
复制代码

使用可选绑定更方便一次打开并执行多个操做:

if let textContainer = self.textContainer {
  // do many things with textContainer
}
复制代码

当命名可选变量和属性时,避免像optionalString或maybeView这样命名它们,由于它们的可选参数已经在类型声明中。

对于可选绑定适当隐藏原名,而不是使用unwrappedView或actualLabel这样的名称。

正确的:

var subview: UIView?
var volume: Double?

// later on...
if let subview = subview, let 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
  }
}
复制代码

懒加载

考虑使用懒加载细粒度控制对象生命周期。对于UIViewController那些加载视图而言,这一点尤为如此。您可使用当即调用的闭包 ()或调用私有工厂方法。

lazy var locationManager: CLLocationManager = self.makeLocationManager()

private func makeLocationManager() -> CLLocationManager {
  let manager = CLLocationManager()
  manager.desiredAccuracy = kCLLocationAccuracyBest
  manager.delegate = self
  manager.requestAlwaysAuthorization()
  return manager
}
复制代码

注释: [unowned self]这里不须要 没有建立保留循环。 位置管理器具备弹出UI的反作用,要求用户得到许可,所以细粒度控制在这里是有意义的。

类型推断

但愿使用简洁的代码,让编译器推断单个实例的常量或变量的类型。类型推断也适用于小(非空)数组和字典。须要时,指定特定的类型如CGFloat或Int16。

正确的:

let message =  “点击按钮”
let currentBounds =  computeViewBounds()
var names = [ “ Mic ”,“ Sam ”,“ Christine ” ]
let maximumWidth : CGFloat =  106.5
复制代码

错误的:

let message : String  =  “点击按钮”
let currentBounds : CGRect =  computeViewBounds()
let names = [ String ]()
复制代码

空数组和字典的类型注释: 对于空数组和字典,使用类型注释。(对于分配了大量多行内容的数组或字典,使用类型标注)

正确的:

var names: [String] = []
var lookup: [String: Int] = [:]
复制代码

错误的:

var names = [String]()
var lookup = [String: Int]()
复制代码

注意:遵循此准则意味着选择描述性名称比之前更重要。

语法糖

与完整的泛型语法相比,更倾向于使用简洁版的类型声明。

正确的:

var deviceModels: [String]
var employees: [Int: String]
var faxNumber: Int?
复制代码

错误的:

var deviceModels: Array<String>
var employees: Dictionary<Int, String>
var faxNumber: Optional<Int>
复制代码

功能与方法

对于不附加到类或类型的不受约束的功能,应该谨慎使用。若是能够,尽量使用‘.’方法而不是不受约束的功能。这有助于可读性和可发现性。 正确的:

let sorted = items.mergeSorted()  // easily discoverable
rocket.launch()  // acts on the model
复制代码

错误的:

let sorted = mergeSort(items)  // hard to discover
launch(&rocket)
复制代码

自由功能异常:

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

内存管理

代码不该该建立引用循环,即便是非生产或教程demo中。分析您的对象图,并使用weak或unowned防止强烈的循环。或者,使用值类型(struct,enum)来彻底防止循环。

延长对象生命周期

使用【weak self】和 guard let strongSelf = self else { return }语句扩展对象生命周期。【weak self】相对于【unowned self】的优点不是特别明显时,不使用self。明显延长使用周期,显然比可选展开更好。

推荐的:

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

不推荐的:

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

不推荐的:

// 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)
}
复制代码

访问控制

文章中的全局访问控制注释可能会分散主题,这而且不是必要的。然而,适当地使用private和fileprivate,增进了清晰度并促进了封装。条件容许的话,尽量使用private而不是fileprivate 。使用扩展可能须要您使用fileprivate。

只有当你须要完整的访问控制规范时才明确的使用open,public及internal。

使用访问控制做为首选属性说明符。访问控制前惟一的条件是 static符,或属性例如@IBAction、@IBOutlet和@discardableResult等。

推荐的:

private let message = "Great Scott!"

class TimeMachine {  
  fileprivate dynamic lazy var fluxCapacitor = FluxCapacitor()
}
复制代码

不推荐的:

fileprivate let message = "Great Scott!"

class TimeMachine {  
  lazy dynamic fileprivate var fluxCapacitor = FluxCapacitor()
}
复制代码

控制流

尽可能使用for-in风格的for循环而不是while-condition-increment风格。

推荐的:

for _ in 0..<3 {
  print("Hello three times")
}

for (index, person) in attendeeList.enumerated() {
  print("\(person) is at position #\(index)")
}

for index in stride(from: 0, to: items.count, by: 2) {
  print(index)
}

for index in (0...3).reversed() {
  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
}
复制代码

Golden Path

当用条件语句编码时,左边的代码应该是"golden" 或 "happy”路径。也就是说,不要嵌套if语句。使用多个返回语句也是能够的,这个guard就是为此创建的。

推荐的:

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 仍是 if let 打开多个可选项,在可能的状况下减小嵌套使用复合版本。例:

推荐的:

guard let number1 = number1,
       let number2 = number2,
       let 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")
 }
复制代码

失败的Guard

Guard声明须要以某种方式退出。通常状况下,这应该是简单的一行语句,如return、throw、break、continue和fatalError()。大量代码的block应该避免使用。若是有多个退出点须要清除代码,请考虑使用defer(延迟)代码块来避免重复清除代码。

分号

Swift在您的任何一个代码语句后都不须要分号。若是你想在一行中组合多个语句,则须要‘;’。 但不建议在一行上写用‘;’分隔的多条语句。

推荐的:

let swift = "not a scripting language"
复制代码

不推荐的:

let swift = "not a scripting language";
复制代码

注意:Swift与JavaScript大相径庭,后者省略分号一般被认为是不安全的。

括号

if 附近的括号不是必须的,应该省略。 推荐的:

if name == "Hello" {
  print("World")
}
复制代码

不推荐的:

if (name == "Hello") {
  print("World")
}
复制代码

在有大量代码的表达式中,有括号可使代码更清晰地读取。

推荐的:

let playerMark = (player == current ? "X" : "O")
复制代码

机构和 Bundle Identifier

凡是xcode有关的,机构应该设置为‘Ray Wenderlich’ ,Bundle Identifier应该设置为com.razeware.TutorialName形式,其中TutorialName是项目的名字。

Swift设计指南原文

相关文章
相关标签/搜索