swift编码规范

Swift 编码规范

<a name="basic-rules"></a>

基本原则

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

1. 清晰

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

2. 简洁

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

3. 本地化

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

<a name="reference-links"></a>

参考资料

<a name="general-rules"></a>

通用规则

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

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
}

  • <span style="color:red">把warnings当作errors对待。</span>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<String>
var employees: Dictionary<Int, String>
var faxNumber: Optional<Int>
  • 尽可能地选用命名函数来代替自定义操作符。

<a name="formatting"></a>

格式

  • 一个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<T, U: SomeProtocol>(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) {
}

<a name="naming"></a>

命名

  • 不要在Swift中使用前缀(e.g. YZT).
  • 类型名字使用Pascal命名法(如 struct, enum, class, typedef, associatedtype, 等)。
  • 函数, 方法, 属性, 常量, 变量, 参数, 枚举值, 使用camel命名法。
  • 当处理一个缩略词或者通常都大写的特定的词,代码中一般也要全部大写。但是如果该词出现在某个名字的最开头,则使用小写。
let htmlBodyContent: String = "<p>Hello, World!</p>"
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<T> { /* ... */ }
class SomeClass<Model> { /* ... */ }
protocol Modelable {
associatedtype Model
}
protocol Sequence {
associatedtype IteratorType: Iterator
}
  • 不要使用缩写形式、短名称、单个字母的名字。
  • 常量、属性、变量和类型(struct、class)名称通常会包含表示类型的后缀(UI控件常用)
class ConnectionTableViewCell: UITableViewCell {
let personImageView: UIImageView
let popupViewController: UIViewController
}

<a name="coding-style"></a>

编码风格

<a name="access-modifiers"></a>

访问修饰符

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

<a name="structs"></a>

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

<a name="enums"></a>

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)

<a name="optionals"></a>

Optional

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

Preferred

if someOptional != nil {
}

Not Preferred

if let _ = someOptional {
}

<a name="properties"></a>

属性

  • 如果是定义一个只读的需要经过计算的属性,那么不需要声明 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()
}

<a name="closures"></a>

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

<a name="collections"></a>

Collection

  • 避免直接使用下标访问数组。如果可能,使用.first.last等访问器。
  • 使用for item in items语法,而不是像for i in 0 .. <items.count
  • 不要使用+ =+运算符来添加单个元素。推荐使用.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))
}

<a name="error-handling"></a>

错误处理

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

<a name="guard"></a>

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 }

<a name="self"></a>

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

<a name="comments"></a>

注释

  • 使用 // 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() {
}

}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,406评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,976评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,302评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,366评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,372评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,457评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,872评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,521评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,717评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,523评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,590评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,299评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,859评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,883评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,127评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,760评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,290评论 2 342

推荐阅读更多精彩内容

  • 注:以下皆为翻译,如有错误或疏漏,请指正。谢谢☺ 简介 (raywenderlich 版)您看到的这份规范可能与其...
    LovelyYilia阅读 4,351评论 1 17
  • 从其他地方整理了一些编码规范的资料,分享给大家。YoY 这我们的首要目标是简洁,可读性和简单性。 1.命名(Nam...
    大脸猫121阅读 1,050评论 2 2
  • 翻译作者:码农网 – 豆照建 没事多看看,规范一下swift 编码习惯。 1. 代码格式 1.1 使用四个空格进行...
    彡廿阅读 835评论 0 2
  • 温德里奇规范 本规范原文链接. 规范只是一些最佳实践, 请酌情使用. 1 正确性 第一条原则, 也是最根本的原则:...
    貘鸣阅读 1,562评论 1 0
  • 1 命名 使用驼峰式给类,方法或者变量等。类命名必须大写,然后方法名和变量应该以小写字母开始。 eg 首选的 pr...
    Marc_Steven阅读 751评论 0 0