这篇Swift风格指南与你看到的其他的指南有所不同,此篇指南主要焦点集中在打印和web展示的可读写上。我们创建此篇风格指南的目的,是为了让我们的图书,教程以及初学者套件中的代码保持优美和一致,即使我们有很多不同的作者共同编写这些图书。
我们的首要目标是一致性,可读性和简洁性。
命名(Naming)
使用驼峰式的描述性命名方式,为类,方法,变量等命名。类名的首字母应该大写,而方法和变量的首字母使用小写字符。
推荐做法:
1 |
private let maximumWidgetCount = 100 |
不推荐做法:
1 |
let MAX_WIDGET_COUNT = 100 |
对于函数和初始化方法,推荐对所有的参数进行有意义的命名,除非上下文已经非常清楚。如果外部参数命名可以使得函数调用更加可读,也应该把外部参数命名包含在内。
1 |
func dateFromString(dateString: String) -> NSDate |
对于方法来说,参照标准的苹果惯例,方法命名含义要引用到第一个参数:
1 |
class Guideline { |
枚举(Enumerations)
使用首字母大写的驼峰命名规则来命名枚举值:
1 |
enum Shape { |
文章(Prose)
当我们在文章中(教程,图书,注释等)需要引用到函数时,需要从调用者的视角考虑,包含必要的参数命名,或者使用_
表示不需要命名的参数。
从你自身实现的
init
中调用convertPointAt(column:row:)
。如果你调用
dateFromString(_:)
,需要保证你提供的输入字符串格式是”yyyy-MM-dd”。如果你需要在
viewDidLoad()
中调用timedAction(delay:perform:)
,记得提供调整后的延迟值和需要处理的动作。
当你遇到疑问时,可以看看Xcode在jump bar
中是如何列出方法名的 —— 我们的风格与此匹配。
类的前缀(Class Prefixes)
Swift类型自动被模块名设置了名称空间,所以你不需要加一个类的前缀。如果两个来自不同模块的命名冲突了,你可以附加一个模块名到类型命名的前面来消除冲突。
1 |
import SomeModule |
空格(Spacing)
- 使用2个空格的缩进比使用tabs更好,可以减少占用空间和帮助防止多次换行。确保在Xcode进行了下图的偏好设置:
-
方法定义的大括号或者其他大括号(if/else/switch/while等)—— 般都放在定义名称的同一行,并且使用一个新的行来结束。
-
提示:你可以通过以下方法重新进行缩进:选择一些代码(或者使用⌘A选择所有),然后按Control-I(或者点击菜单栏 Editor\Structure\Re-Indent)。一些Xcode模板代码使用的缩进是4个空格,所以这种方法可以很好的修复缩进。
推荐做法:
1 |
if user.isHappy { |
不推荐做法:
1 |
if user.isHappy |
注释(Comments)
当你需要时,使用注释来解释一段特定的代码段的作用。注释必须保证更新或者及时删除。
避免在代码中使用块注释,代码尽可能自己能表达含义。以下情况除外:当使用注释来生成文档时。
类和结构体(Classes and Structures)
选择使用谁?(Which one to use?)
请记住,结构体是值类型。使用结构体并没有一个标识。一个数组包含[a,b,c]
和另外一个数组同样包含[a,c]
是完全一样的,它们完全可以交换使用。使用第一个还是使用第二个无关紧要,因为它们代表的是同一个东西。这就是为什么数组是结构体。
类是引用类型。使用类是有一个标识或者有一个特定的生命周期。你需要对一个人类建模为一个类,因为两个不同的人的实例,是两个不同的东西。只是因为两个人有同样的名字和生日,也不能断定这两个人是一样的。但是人的生日是一个结构体,因为日期1950-3-3和另外一个日期1950-3-3是相同的。日期不需要一个标识。
有时,一些事物应该定义为结构体,但是需要兼容AnyObject或者已经在以前的历史版本中定义为类(NSDate
,NSSet
)。尽可能的尝试遵守这些规则。
定义的案例(Example definition)
以下是一个风格很好的类定义:
1 |
class Circle: Shape { |
以上例子遵循了以下风格规范:
- 指定属性,变量,常量,参数定义或者其他定义的类型,在冒号后面,紧跟着一个空格,而不是把空格放在冒号前面。比如:
x: Int
和Circle: Shape
。 - 如果能表示相同的目的和上下文,可以在同一行定义多个变量和结构体。
- 缩进getter,setter的定义和属性观察器的定义。
- 不需要添加
internal
这样的默认的修饰符。同样的,不需要在重写一个方法时添加访问修饰符。
Self的使用(Use of Self)
为了保持简洁,避免使用 self 关键词,Swift 不需要使用self
来访问对象属性和调用对象方法。
必须使用self
来区分构造器中属性命名和参数命名,还有在闭包表达式中引用属性值(编译器需要区分):
1 |
class BoardLocation { |
协议遵守(Protocol Conformance)
当我们对一个类添加协议时,推荐使用一个单独的类扩展来实现协议的方法。这可以保持协议相关的方法聚合在一起,同时也可以简单的标识出一个协议对应类中需要实现哪些对应的方法。
同时,别忘了添加// MARK:,注释可以使得代码组织的更好!
推荐做法:
1 |
class MyViewcontroller: UIViewController { |
不推荐做法:
1 |
class MyViewcontroller: UIViewController,UITableViewDataSource,UIScrollViewDelegate { |
计算属性(Computed Properties)
为了保持简洁,如果一个计算属性是只读的,请忽略掉get语句。只有在需要定义set语句的时候,才提供get语句。
推荐做法:
1 |
var diameter: Double { |
不推荐做法:
1 |
var diameter: Double { |
函数声明(Function Declarations)
保证短的函数定义在同一行中,并且包含左大括号:
1 |
func reticulateSplines(spline: [Double]) -> Bool { |
在一个长的函数定义时,在适当的地方进行换行,同时在下一行中添加一个额外的缩进:
1 |
func reticulateSplines(spline: [Double],adjustmentFactor: Double, |
如果闭包表达式参数在参数列表中的最后一个时,使用尾部闭包表达式。给定闭包参数一个描述性的命名。
推荐做法:
1 |
UIView.animateWithDuration(1.0) { |
不推荐做法:
1 |
UIView.animateWithDuration(1.0,animations: { |
当单个闭包表达式上下文清晰时,使用隐式的返回值:
1 |
attendeeList.sort { a,b in |
类型(Types)
尽可能使用 Swift 原生类型。Swift 提供到 Objective-C 类型的桥接,所以你仍然可以使用许多需要的方法。
推荐做法:
1 |
let width = 120.0 // Double |
不推荐做法:
1 |
let width: NSNumber = 120.0 // NSNumber |
在 Sprite Kit 代码中,使用 CGFloat 可以使得代码更加简明,避免很多转换。
常量(Constants)
常量定义使用let
关键字,变量定义使用var
关键字,如果变量的值不需要改变,请尽量使用let
关键字。
提示:一个好的技巧是,使用let
定义任何东西,只有在编译器告诉我们值需要改变的时候才改成var
定义。
可选类型(Optionals)
当nil值是可以接受的时候时,定义变量和函数返回值为可选类型(?)。
当你确认变量在使用前已经被初始化时,使用!来显式的拆包类型,比如在viewDidLoad
中会初始化subviews
。
当你访问一个可选值时,如果只需要访问一次或者在可选值链中有多个可选值时,请使用可选值链:
1 |
self.textContainer?.textLabel?.setNeedsDisplay() |
当需要很方便的一次性拆包或者添加附加的操作时,请使用可选值绑定:
1 |
if let textContainer = self.textContainer { |
当我们命名一个可选变量和属性时,避免使用诸如optionalString
和maybeView
这样的命名,因为可选值的表达已经在类型定义中了。
在可选值绑定中,直接映射原始的命名比使用诸如unwrappedView
和actualLabel
要好。
推荐做法:
1 |
var subview: UIView? |
不推荐做法:
1 |
var optionalSubview: UIView? |
结构体构造器(Struct Initializers)
使用原生的 Swift 结构体构造器,比老式的几何类(CGGeometry)的构造器要好。
推荐做法:
1 |
let bounds = CGRect(x: 40,y: 20,width: 120,height: 80) |
不推荐做法:
1 |
let bounds = CGRectMake(40,20,120,80) |
推荐使用结构体限定的常量CGRect.infiniteRect
,CGRect.nullRect
等,来替代全局常量CGRectInfinite
,CGRectNull
等。对于已经存在的变量,可以直接简写成.zeroRect
。
类型推断(Type Inference)
推荐使用更加紧凑的代码,让编译器能够推断出常量和变量的类型。除非你需要定义一个特定的类型(比如CGFloat
和Int16
),而不是默认的类型。
推荐做法:
1 |
let message = "Click the button" |
不推荐做法:
1 |
let message: String = "Click the button" |
注意:遵守这条规则意味选择描述性命名比之前变得更加重要。
语法糖(Syntactic Sugar)
推荐使用类型定义简洁的版本,而不是全称通用语法。
推荐做法:
1 |
var deviceModels: [String] |
不推荐做法:
1 |
var deviceModels: Array<String> |
控制流(Control Flow)
推荐循环使用for-in
表达式,而不使用for-condition-increment
表达式。
推荐做法:
1 |
for _ in 0..<3 { |
不推荐做法:
1 |
for var i = 0; i < 3; i++ { |