swift编码规范

Swift 编码规范

  • 基本原则
  • 参考资料
  • 通用规则
  • 格式
  • 命名
  • 编码风格
  • 访问修饰符
  • Enum
  • Optional
  • 属性
  • Closure
  • Collection
  • 错误处理
  • Guard
  • Self
  • 注释

基本原则

本章所含条款是代码规范的基本思想,适用于所有的编程语言。

1. 清晰

  • 代码要简单易懂
  • 代码要没有歧义

2. 简洁

  • 做技术的人不说废话
  • 在不违反第一条原则的前提下有效

3. 本地化

  • 代码要更像Swift
  • 要和Swift的代码风格保持一致
  • 要和Swift的编程思想保持一致
  • Swift的代码追求接近自然语言

参考资料

  • Swift API 设计指南
  • raywenderlich的代码规范
  • linkedIn的代码规范
  • dialect of Markdown

通用规则

  • 代码应 简明、清晰
  • 在名字中包含所有需要的单词。
    例:考虑一个移除集合中移除指定位置的元素的方法。

Preferred:

public mutating func remove(at position: Index) -> Element

Not Preferred:

public mutating func remove(position: Index) -> Element

employees.remove(x) // 不明确: 我们是在删除 x 吗?
  • 删除不需要的单词。名字中的每个单词都应在使用处表明主要信息。

Preferred:

public mutating func remove(member: Element) -> Element?

Not Preferred:

public mutating func removeElement(member: Element) -> Element?
  • 移除无用的代码,包括Xcode模板代码和占位内容。有个例外情况,当你的教程希望别人去使用注释代码。只是单纯调用了super方法的应该被删掉,以及空的没用的UIApplicationDelegate方法。

Preferred:

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

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
}

  • 把warnings当作errors对待。warning包含了许多编写代码的建议,比如说:不用 ++-- 操作符, 不用C风格的for循环, 不用字符串来表示 selector 等。
  • 使方法或函数名在调用的时,符合英语语法规范。
x.insert(y, at: z) // “x, insert y at z”
x.subViews(havingColor: y) // “x's subviews having color y”
x.capitalizingNouns() // “x, capitalizing nouns”
  • 尽量使用方法和属性,而不是普通函数。普通函数仅适用于一些特定情况:
  • 当没有明显的 self:
min(x, y, z)
  • 当函数为无约束泛型:
print(x)
  • 当函数的写法是公认记法的一部分:
sin(x)
  • 不要写过长的if else
  • 尽可能地使用 let 来代替 var 。
  • 不要使用返回值重载。
  • 保持最小导入。比如:当导入Foundation能够满足的时候不导入UIKit
  • 代码应该是尽可能的自注释。
  • 总是使用Swift的原生类型。
  • 不要使用分号,除了for-conditional-increment

Preferred:

let swift = "not a scripting language"

Not Preferred:

let swift = "not a scripting language";
  • 使用weak,不使用unowned。使用 [weak self]guard let strongSelf = self else { return } idiom.

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)
}
  • 使用常量、变量的类型推断

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]()
  • 如果你的函数需要返回多个参数,那么尽可能地使用 Tuple 来代替 inout 参数。如果你会多次使用某个元组,那么应该使用 typealias 设置别名。如果返回的参数超过三个,那么应该使用结构体或者类来替代。
func pirateName() -> (firstName: String, lastName: String) {
return ("Guybrush", "Threepwood")
}

let name = pirateName()
let firstName = name.firstName
let lastName = name.lastName
  • 不要使用 labeled breaks
  • 在编写某个方法的时候注意考虑下这个方法是否有可能被复写,如果不可能被复写那么应该使用final修饰符。一般而言,加上 final 修饰符后会提高编译的效率,所以应该尽可能地使用该修饰符。
  • 使用语法糖

Preferred:

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

Not Preferred:

var deviceModels: Array
var employees: Dictionary
var faxNumber: Optional
  • 尽可能地选用命名函数来代替自定义操作符。

格式

  • 一个tab键的大小为4个space
  • 确保每个文件的最后都以一个新的空行结束。即回车
  • 任何一个地方都不要以空格键结尾。通过Xcode可设置 (Xcode->Preferences->Text Editing->Automatically trim trailing whitespace + Including whitespace-only lines).
  • 在方法间应该有空的一行。
  • 方法花括号和其他花括号(if/else/switch/while etc.) 左花括号和在方法体在同一行,右花括号在新的一行。
class SomeClass {
func someMethod() {
if x == y {
/* ... */
} else if x == z {
/* ... */
} else {
/* ... */
}
}
}
  • 冒号前面无空格,后面有一个空格。三目运算符 ? : 和空的字典[:]除外。
// specifying type
let pirateViewController: PirateViewController

// dictionary syntax (note that we left-align as opposed to aligning colons)
let ninjaDictionary: [String: AnyObject] = [
"fightLikeDairyFarmer": false,
"disgusting": true
]

// declaring a function
func myFunction(firstArgument: U, secondArgument: T) where T.RelatedType == U {
}

// calling a function
someFunction(someArgument: "Kitten")

// superclasses
class PirateViewController: UIViewController {
}
  • 逗号前面无空格,后面有一个空格。
let myArray = [1, 2, 3, 4, 5]
  • 在一个二元/三元运算符(如 +, ==, 或者 ->)的前后应该各有一个空格,而在(的后面 和 )的前面一般不要出现空格。
let myValue = 20 + (30 / 2) * 3
if 1 + 1 == 3 {
fatalError("The universe is broken.")
}
func pancake(with syrup: Syrup) -> Pancake {
/* ... */
}
  • 定义和调用多个参数的方法时,方法参数写在同一行,不要换行。
someFunctionWithManyArguments(firstArgument: "Hello, I am a string", secondArgument: resultFromSomeFunction(), thirdArgument: someOtherLocalProperty)
  • 不要在控制流逻辑判断的时候加上圆括号

Preferred

if x == y {
}

Not Preferred

if (x == y) {
}

命名

  • 不要在Swift中使用前缀(e.g. YZT).
  • 类型名字使用Pascal命名法(如 struct, enum, class, typedef, associatedtype, 等)。
  • 函数, 方法, 属性, 常量, 变量, 参数, 枚举值, 使用camel命名法。
  • 当处理一个缩略词或者通常都大写的特定的词,代码中一般也要全部大写。但是如果该词出现在某个名字的最开头,则使用小写。
let htmlBodyContent: String = "

Hello, World!

" let profileId: Int = 1 class URLFinder { }
  • protocol的名字应该是个名词。举例:CollectionWidgetFactory。Protocols名字应该是以ing,-able或者-ible结尾。比如:EquatableResizing
  • 变量、参数、关联类型依据作用对其进行命名,而不是基于它们的类型。

Preferred:

var greeting = "Hello"
protocol ViewController {
associatedtype ContentView: View
}
class ProductionLine {
func restock(from supplier: WidgetFactory)
}    

Not Preferred:

var string = "Hello"
protocol ViewController {
associatedtype ViewType: View
}
class ProductionLine {
func restock(from widgetFactory: WidgetFactory)
}
  • 如果某个关联类型和它的协议联系非常紧密,导致它的协议名就是它自身的名字,那就给关联类型的名字加上Type避免冲突:
protocol Sequence {
associatedtype IteratorType : Iterator
}
  • 常量
  • 不需要扩展的常量,定义成enum类型,例如
public enum UIBackgroundRefreshStatus: Int {
case restricted
case denied 
case available
}
  • 需要扩展的常量,并且很多的时候,定义新的常量类型(struct),并且把这些常量写在这个类型里,例如
class NSNotification {

public struct Name {
init(_ rawValue: String)

static let UIApplicationDidBecomeActive: NSNotification.Name
static let UIApplicationDidEnterBackground: NSNotification.Name
}
}
  • 对于泛型及相关类型,用一个大写字母或Pascal命名法。如果有冲突,可以添加一个Type后缀。常用的范型符号,例如T,U,V,Item,Element.
class SomeClass { /* ... */ }
class SomeClass { /* ... */ }
protocol Modelable {
associatedtype Model
}
protocol Sequence {
associatedtype IteratorType: Iterator
}
  • 不要使用缩写形式、短名称、单个字母的名字。
  • 常量、属性、变量和类型(struct、class)名称通常会包含表示类型的后缀(UI控件常用)
class ConnectionTableViewCell: UITableViewCell {
let personImageView: UIImageView
let popupViewController: UIViewController
}

编码风格

访问修饰符

  • 将访问修饰符放在关键字的第一位。
private static let myPrivateNumber: Int
  • 一般,不需要编写internal访问修饰符关键字,因为它是默认的。
  • 尽可能的使用private代替 fileprivate.
  • 如果你打算让类在特定的module之外还可以被继承,则用open,反之则用public

Struct

  • 使用原生的Swift struct 构造器

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)
  • 使用 CGRect.infinite, CGRect.null, 等, 而不是 CGRectInfinite, CGRectNull

Enum

  • 在使用枚举类型作为switch的参数的时候,避免引入 default 关键字,而应该将没有使用的情形放到下面然后使用break关键字来避免被执行。
  • Swift中默认会在每个case的结尾进行break,因此没必要的时候不需要显式地声明break关键字。
  • 优先使用譬如 case 1, 2, 3: 这样的列表表达式而不是使用fallthrough关键字。
  • 根据 Apple's API Design Guidelines for Swift 3,使用小写驼峰法命名枚举值。
enum Shape {
case rectangle
case square
case rightTriangle
case equilateralTriangle
}
  • 尽量不要写出Enum名称

Preferred

imageView.setImageWithURL(url, type: .person)

Not Preferred

imageView.setImageWithURL(url, type: AsyncImageView.Type.person)

Optional

  • 不要使用 as! 或者 try!.
  • 如果你只是需要判断可选项的值是否为nil,则显式的检查其是否为nil, 而不需要使用if let语法。

Preferred

if someOptional != nil {
}

Not Preferred

if let _ = someOptional {
}

属性

  • 如果是定义一个只读的需要经过计算的属性,那么不需要声明 get {}
var computedProperty: String {
if someBool {
return "I'm a mighty pirate!"
}
return "I'm selling these fine leather jackets."
}
  • 如果你的方法没有参数,也没有任何副作用,并且返回一些对象或者值,这个时候使用可计算属性代替方法。
  • 尽管你可以在 willSet / didSet 以及 set 方法中使用自定义的名称,不过建议还是使用默认的 newValue / oldValue 变量名
var storedProperty: String = "I'm selling these fine leather jackets." {
willSet {
print("will set to \(newValue)")
}
didSet {
print("did set from \(oldValue) to \(storedProperty)")
}
}

var computedProperty: String  {
get {
if someBool {
return "I'm a mighty pirate!"
}
return storedProperty
}
set {
storedProperty = newValue
}
}
  • 定义一个单例属性:
class PirateManager {
static let shared = PirateManager()
}

Closure

  • 如果可能,参数名称必须与开环大括号保持在同一行。
  • 尽量使用 trailing closure。
  • 链式语法的trailing closures要注意表达清晰,便于上下文阅读。 例如:
let value = numbers.map { $0 * 2 }.filter { $0 % 3 == 0 }.indexOf(90)

let value = numbers
.map {$0 * 2}
.filter {$0 > 50}
.map {$0 + 10}
  • 在escaping闭包中直接使用self的时候要小心,可能会引起循环引用。
myFunctionWithEscapingClosure() { [weak self] (error) -> Void in
guard let strongSelf = self else {
return
}

strongSelf.doSomething()
}

Collection

  • 避免直接使用下标访问数组。如果可能,使用.first.last等访问器。
  • 使用for item in items语法,而不是像for i in 0 ..
  • 不要使用+ =+运算符来添加单个元素。推荐使用.append().append(contentsOf:),因为这些在Swift的当前状态下性能更高(至少对于编译)。
  • 变量初始化为空数组和字典的写法:

Preferred:

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

Not Preferred:

var names = [String]()
var lookup = [String: Int]()
  • 尽可能地使用 map , filter , reduce , forEach 的组合来进行集合的转换等操作,尽量使用Trailing Closure。

Preferred

let stringOfInts = [1, 2, 3].flatMap { "\($0)" }

Not Preferred

var stringOfInts: [String] = []
for integer in [1, 2, 3] {
stringOfInts.append(String(integer))
}

错误处理

  • 当结果应该在语义上可能是nil,而不是在检索结果时出现错误,返回一个 Optional 而不是 Error

Guard

  • 当guard表达式要退出的时候,通常表达体内应该一行代码结束,例如return, throw, break, continue, and fatalError()。使用defer去清理代码。
  • 使用guard来过滤nil
  • 使用guard来当过滤可选项时
  • 如果在两个不同的状态之间选择,使用if语句而不是guard
  • 不要使用单行guard语句。

Preferred

guard let thingOne = thingOne else {
return
}

Not Preferred

guard let thingOne = thingOne else { return }

Self

  • 避免使用self,因为Swift没必要通过它去使用property或者调用方法。
  • 在初始化方法里,在property和argument之间使用self,和在closure expressions的相关property(编译器是必须):
class BoardLocation {
let row: Int, column: Int

init(row: Int, column: Int) {
self.row = row
self.column = column

let closure = {
print(self.row)
}
}
}

注释

  • 使用 // MARK: 注释将代码合理分块。
  • 使用新的 - parameter 语法替代旧的 :param: 语法 (注意小写 parameter 而不是 Parameter). 更多注释细节参考 the documentation on Swift Markup 。
  • 对于复杂的类,可以描述类的使用并且附带一些简单的例子。Swift的文档注释支持markdown语法。
/**
## Feature Support

This class does some awesome things. It supports:

- Feature 1
- Feature 2
- Feature 3

## Examples

Here is an example use case indented by four spaces because that indicates a
code block:

let myAwesomeThing = MyAwesomeClass()
myAwesomeThing.makeMoney()

## Warnings

There are some things you should be careful of:

1. Thing one
2. Thing two
3. Thing three
*/
class MyAwesomeClass {
/* ... */
}
  • 如果注释中提到代码, 使用``修饰
/**
This does something with a `UIViewController`, perchance.
- warning: Make sure that `someValue` is `true` before running this function.
*/
func myFunction() {
/* ... */
}
  • 保证文档的注释尽可能的简洁
  • //都要跟着一个空格。
  • 注释要放在单独的行中。
  • 使用 // MARK: - whatever时,要在注释完后留个空行。
class Pirate {

// MARK: - instance properties

private let pirateName: String

// MARK: - initialization

init() {
}

}

你可能感兴趣的:(swift编码规范)