Swift 编码规范(中文)
注:以下皆为翻译,如有错误或疏漏,请指正。谢谢☺
简介
(raywenderlich 版)
您看到的这份规范可能与其他的有所不同,因为这份关注点是web显示和打印。我们创建这份规范已保证我们书中的代码,教程和 sdk 一致。
Objective-C 代码规范:Objective-C Style Guide.
Table of Contents(目录)
- Correctness----正确性
- Naming----命名
- Code Organization----代码组织
- Spacing----空格
- Comments----注释
- Classes and Structures----类和结构体
- Function Declarations----函数定义
- Closure Expressions----闭包表达式
- Types----类型
- Functions vs Methods----函数和方法
- Memory Management----内存管理
- Access Control----访问控制
- Control Flow----控制流
- Golden Path----黄金路线
- Semicolons----分号
- Parentheses----圆括号
- Copyright Statement
- Smiley Face
- Credits
<a id="correctness"></a>
Correctness(正确性)
将 warnings 视为 errors. 比如不要用 ++ 、-- 或 c 风格的 循环 或 string 型的 selectors.
<a id="naming"></a>
Naming(命名)
classes, structures, enumerations and protocols 等类型名字以首字母大写驼峰命名。变量、方法名以小写驼峰方式命名
Preferred(推荐):
private let maximumWidgetCount = 100
class WidgetContainer {
var widgetButton: UIButton
let widgetHeightPercentage = 0.85
}
Not Preferred(不推荐):
let MAX_WIDGET_COUNT = 100
class app_widgetContainer {
var wBut: UIButton
let wHeightPct = 0.85
}
尽量避免“简称”和“缩略词”,通用缩略词应该整体大写或整体小写
Preferred(推荐)
let urlString: URLString
let userID: UserID
Not Preferred(不推荐)
let uRLString: UrlString
let userId: UserId
初始化方法或其他方法,每个参数前都应该有明确说明。
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)
遵循 Apple 官方习俗为方法命名。
class Counter {
func combineWith(otherCounter: Counter, options: Dictionary?) { ... }
func incrementBy(amount: Int) { ... }
}
<a id="protocols"></a>
Protocols(委托)
遵循Apple's API Design Guidelines。 protocols
描述某物的应该是名词。如:Collection
, WidgetFactory
。
protocols是描述能力的应该以-ing, -able或 -ible结尾。如: Equatable
, Resizing
。
<a id="enumerations"></a>
Enumerations(枚举)
遵循 swift 3 Apple's API Design Guidelines
。每个枚举值用小写字母开始。
enum Shape {
case rectangle
case square
case rightTriangle
case equilateralTriangle
}
<a id="prose"></a>
Prose
When referring to functions in prose (tutorials, books, comments) include the required parameter names from the caller's perspective or _
for unnamed parameters. Examples:
Call
convertPointAt(column:row:)
from your owninit
implementation.If you call
dateFromString(_:)
make sure that you provide a string with the format "yyyy-MM-dd".If you call
timedAction(afterDelay:perform:)
fromviewDidLoad()
remember to provide an adjusted delay value and an action to perform.You shouldn't call the data source method
tableView(_:cellForRowAtIndexPath:)
directly.
This is the same as the #selector
syntax. When in doubt, look at how Xcode lists the method in the jump bar – our style here matches that.
<a id="class-prefixes"></a>
Class Prefixes(Class 前缀)
swift 被自动包含了 module 前缀,你不需要添加额外前缀。如果两个不同的 module 的两个名字有冲突,你可以通过在 module 前添加前缀来消除该冲突。当然,仅当 module 名称可能冲突的时候才为他特定设置一个名称(这种情况应该很少见)。
import SomeModule
let myClass = MyModule.UsefulClass()
<a id="selectors"></a>
Selectors(选择器)
Preferred(推荐):
let sel = #selector(viewDidLoad)
Not Preferred(不推荐):
let sel = #selector(ViewController.viewDidLoad)
<a id="generics"></a>
Generics(泛型)
泛型参数应该描述清楚所规定的泛型。当不确定泛型类型是才使用传统的大写字母 如T
, U
, or V
表示泛型。
Preferred(推荐):
struct Stack<Element> { ... }
func writeTo<Target: OutputStream>(inout target: Target)
func max<T: Comparable>(x: T, _ y: T) -> T
Not Preferred(不推荐):
struct Stack<T> { ... }
func writeTo<target: OutputStream>(inout t: target)
func max<Thing: Comparable>(x: Thing, _ y: Thing) -> Thing
<a id="language"></a>
Language(语言)
用美式英语拼写才符合Apple's API
。
Preferred(推荐):
let color = "red"
Not Preferred(不推荐):
let colour = "red"
<a id="code-organization"></a>
Code Organization(代码组织)
Use extensions to organize your code into logical blocks of functionality. Each extension should be set off with a // MARK: -
comment to keep things well-organized.
<a id="protocol-conformance"></a>
Protocol Conformance
每个 protocol 的实现分别对应一个 extension 的方式实现。
Preferred(推荐):
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
}
Not Preferred(不推荐):
class MyViewcontroller: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
// all methods
}
由于编译器不允许一个类重新申明 protocol 实现,因此,不是一直要求每个 protocols 都要分离出一个 extension.特别是当该 class 是个最终 class 或该 class 方法很少。
对UIKit view controllers,将lifecycle, custom accessors, and IBAction等方法单独放在一个 class extension 中。
<a id="unused-code"></a>
Unused Code(无用代码)
无用的code ,包括 Xcode 注释都应该被移除。空方法应该被移除。
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
}
Preferred(推荐):
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Database.contacts.count
}
<a id="minimal-imports"></a>
Minimal Imports(最少引入)
不要引用UIKit
和 Foundation
<a id="spacing"></a>
Spacing(空格)
- 用4个空格而不是 tabs调整对其,xcode 中设置如下:
- 用命令Ctr+I重新排列代码。
Preferred(推荐):
if user.isHappy {
// Do something
} else {
// Do something else
}
Not Preferred(不推荐):
if user.isHappy
{
// Do something
}
else {
// Do something else
}
-
每两个方法之间应该空一行。
-
冒号左边无空格,右边有一个空格。
? :
和 空字典[:]
例外
Preferred(推荐):
class TestDatabase: Database {
var data: [String: CGFloat] = ["A": 1.2, "B": 3.2]
}
Not Preferred(不推荐):
class TestDatabase : Database {
var data :[String:CGFloat] = ["A" : 1.2, "B":3.2]
}
<a id="comments"></a>
Comments(注释)
必要的时候添加注释注明 为什么 。
避免一大段注释解释一行代码,代码应该有自我解释性Exception: This does not apply to those comments used to generate documentation.
<a id="classes-and-structures"></a>
Classes and Structures(类和结构体)
<a id="which-one-to-use"></a>
用哪一个?
struct 是值类型,当你不需要唯一身份的时候用struct.arrayOne = [a, b, c] 和arrayTwo = [a, b, c]意义是一样的。不需要在乎他们是否是同一个 array。这就是用 struct 的原因。
class 是引用类型。需要唯一标识或有生命周期的才用 class。你可以把一个人定义为一个 class 实例,因为每个人都是不一样的。仅仅只有名字和生日相同的两人也不是同一个人。
有时候,应该是 struct 确是 class ,如以下情况 NSDate
NSSet
<a id="example-definition"></a>
Example Definition(示例)
以下是一个好的 class 代码示范:
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)
}
func describe() -> String {
return "I am a circle at \(centerString()) with an area of \(computeArea())"
}
override func computeArea() -> Double {
return M_PI * radius * radius
}
private func centerString() -> String {
return "(\(x),\(y))"
}
}
以上示例演示遵循如下指导:
- 声明的类型用
:
衔接在后面 - 多个变量公用 关键字 var let 可以声明在同一行
- 缩进 getter setter willSet didSet
- 变量前不需要添加
internal
关键字,因为这是默认的。同样不需要重复写重写方法内的代码
<a id="use-of-self"></a>
Use of Self(self 用法)
避免使用self
去调用属性或方法。
仅在初始化方法的参数名和属性名相同时用self
class BoardLocation {
let row: Int, column: Int
init(row: Int, column: Int) {
self.row = row
self.column = column
let closure = {
print(self.row)
}
}
}
<a id="computed-properties"></a>
Computed Properties(计算属性)
如果一个计算属性的返回值很简单,可以省去 get{}。有 set{} 就一定要有 get{}
Preferred(推荐):
var diameter: Double {
return radius * 2
}
Not Preferred(不推荐):
var diameter: Double {
get {
return radius * 2
}
}
<a id="final"></a>
Final(关键字)
当你不希望该类被继承时,用 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
}
}
<a id="function-declarations"></a>
Function Declarations(函数声明)
保持短方法名在一行
func reticulateSplines(spline: [Double]) -> Bool {
// reticulate code goes here
}
如果方法名换行,下一行添加一个缩进距离
func reticulateSplines(spline: [Double], adjustmentFactor: Double,
translateConstant: Int, comment: String) -> Bool {
// reticulate code goes here
}
<a id="closure-expressions"></a>
Closure Expressions(闭包表达式)
闭包放在最后面
Preferred(推荐):
UIView.animateWithDuration(1.0) {
self.myView.alpha = 0
}
UIView.animateWithDuration(1.0,
animations: {
self.myView.alpha = 0
},
completion: { finished in
self.myView.removeFromSuperview()
}
)
Not Preferred(不推荐):
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
}
链式方法后面跟随闭包,方法名应该清晰明了。
let value = numbers.map { $0 * 2 }.filter { $0 % 3 == 0 }.indexOf(90)
let value = numbers
.map {$0 * 2}
.filter {$0 > 50}
.map {$0 + 10}
<a id="types"></a>
Types(类型)
如无必要,用 swift 类型,不要用 oc 类型
Preferred(推荐):
let width = 120.0 // Double
let widthString = (width as NSNumber).stringValue // String
Not Preferred(不推荐):
let width: NSNumber = 120.0 // NSNumber
let widthString: NSString = width.stringValue // NSString
在Sprite Kit编程中,用CGFloat
以便代码更简明,避免各种转换。
<a id="constants"></a>
Constants
常量用 let
,变量用var
。
Tip: A good technique is to define everything using let
and only change it to var
if the compiler complains!
你可以将常量定义到一个类型中而不是作为实例的类型属性。 类型属性用static let
。
Preferred(推荐):
enum Math {
static let e = 2.718281828459045235360287
static let pi = 3.141592653589793238462643
}
radius * Math.pi * 2 // circumference
Note:
The advantage of using a case-less enumeration is that it can't accidentally be instantiated and works as a pure namespace.
Not Preferred(不推荐):
let e = 2.718281828459045235360287 // pollutes global namespace
let pi = 3.141592653589793238462643
radius * pi * 2 // is pi instance data or a global constant?
<a id="static-methods-and-variable-type-properties"></a>
Static Methods and Variable Type Properties
static方法和类型的功能类似全局函数和全局变量
<a id="optionals"></a>
Optionals(可选值)
声明一个函数的某个参数可以为 nil 时,用?
当你确定某个变量在使用时已经确定不是 nil 时,在后面加!
......
self.textContainer?.textLabel?.setNeedsDisplay()
Use optional binding when it's more convenient to unwrap once and perform multiple operations:
if let textContainer = self.textContainer {
// do many things with textContainer
}
命名一个 optional 变量,避免使用optionalString
或 maybeView
,因为他们已经在声明时体现出来了。
optional 绑定,使用原始名称而不是unwrappedView
或 actualLabel
Preferred(推荐):
var subview: UIView?
var volume: Double?
// later on...
if let subview = subview, volume = volume {
// do something with unwrapped subview and volume
}
Not Preferred(不推荐):
var optionalSubview: UIView?
var volume: Double?
if let unwrappedSubview = optionalSubview {
if let realVolume = volume {
// do something with unwrappedSubview and realVolume
}
}
<a id="struct-initializers"></a>
Struct Initializers(结构体初始化)
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)
Prefer the struct-scope constants CGRect.infinite
, CGRect.null
, etc. over global constants CGRectInfinite
, CGRectNull
, etc. For existing variables, you can use the shorter .zero
.
<a id="lazy-initialization"></a>
Lazy Initialization(懒加载)
Consider using lazy initialization for finer grain control over object lifetime. This is especially true for UIViewController
that loads views lazily. You can either use a closure that is immediately called { }()
or call a private factory method. Example:
用懒初始化
lazy var locationManager: CLLocationManager = self.makeLocationManager()
private func makeLocationManager() -> CLLocationManager {
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.delegate = self
manager.requestAlwaysAuthorization()
return manager
}
Notes:
-
[unowned self]
is not required here. A retain cycle is not created. - Location manager has a side-effect for popping up UI to ask the user for permission so fine grain control makes sense here.
- 不需要使用
[unowned self]
,因为没有创建循环引用 - ......
<a id="type-inference"></a>
Type Inference(类型)
Prefer compact code and let the compiler infer the type for constants or variables of single instances. Type inference is also appropriate for small (non-empty) arrays and dictionaries. When required, specify the specific type such as CGFloat
or Int16
.
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]()
<a id="type-annotation-for-empty-arrays-and-dictionaries"></a>
Type Annotation for Empty Arrays and Dictionaries
用以下方式创建 array dictionary
Preferred(推荐):
var names: [String] = []
var lookup: [String: Int] = [:]
Not Preferred(不推荐):
var names = [String]()
var lookup = [String: Int]()
NOTE: Following this guideline means picking descriptive names is even more important than before.
<a id="syntactic-sugar"></a>
Syntactic Sugar(语法窍门)
推荐用短语义声明
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 id="functions-vs-methods"></a>
Functions vs Methods(函数 方法)
class 或类型以外的自由函数应该单独使用。如果可以,用方法而不是函数。这样会更容易获得和发现。
自由函数不应该跟任何类型或实例关联。
Preferred(推荐)
let sorted = items.mergeSort() // easily discoverable
rocket.launch() // clearly acts on the model
Not Preferred(不推荐)
let sorted = mergeSort(items) // hard to discover
launch(&rocket)
Free Function Exceptions
let tuples = zip(a, b) // feels natural as a free function (symmetry)
let value = max(x,y,z) // another free function that feels natural
<a id="memory-management"></a>
Memory Management(内存管理)
代码尽量避免循环引用,避免强引用,用weak
和unowned
引用。用值类型避免循环引用。
<a id="extending-lifetime"></a>
Extending object lifetime(扩展生命周期)
用[weak self]
和 guard let strongSelf = self else { return }
模式扩展生命周期。用[weak self]
比 [unowned self]
更好。
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)
}
<a id="access-control"></a>
Access Control(访问控制)
Full access control annotation in tutorials can distract from the main topic and is not required. Using private
appropriately, however, adds clarity and promotes encapsulation. Use private
as the leading property specifier. The only things that should come before access control are the static
specifier or attributes such as @IBAction
and @IBOutlet
.
......
Preferred(推荐):
class TimeMachine {
private dynamic lazy var fluxCapacitor = FluxCapacitor()
}
Not Preferred(不推荐):
class TimeMachine {
lazy dynamic private var fluxCapacitor = FluxCapacitor()
}
<a id="control-flow"></a>
Control Flow(控制流)
更推荐 for-in
而不是 while-condition-increment
Preferred(推荐):
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)
}
Not Preferred(不推荐):
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
}
<a id="golden-path"></a>
Golden Path(黄金路线)
多个条件判断时,不要多个if
嵌套,用 guard
Preferred(推荐):
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
}
Not Preferred(不推荐):
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
}
}
判断多个 optional 时,用 guard
或 if let
。减少嵌套。
Preferred(推荐):
guard let number1 = number1, number2 = number2, number3 = number3 else { fatalError("impossible") }
// do something with numbers
Not Preferred(不推荐):
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")
}
<a id="failing-guards"></a>
Failing Guards
Guard 要求以某种方式退出。一般可以用 return
, throw
, break
, continue
, and fatalError()
。避免大量代码
<a id="semicolons"></a>
Semicolons(分号)
swift 不要求写;
,仅仅在你把多个语句写在同一行时才要求;
不要把多个状态语句写在同一行
for-conditional-increment
才会将多个语句写在同一行。但是 swift 推荐用for-in
Preferred(推荐):
let swift = "not a scripting language"
Not Preferred(不推荐):
let swift = "not a scripting language";
NOTE: generally considered unsafe
<a id="parentheses"></a>
Parentheses(圆括号)
圆括号也不是必须的
Preferred(推荐):
if name == "Hello" {
print("World")
}
Not Preferred(不推荐):
if (name == "Hello") {
print("World")
}
<a id="smiley-face"></a>
Smiley Face(笑脸)
笑脸的正确表示方法
Preferred(推荐):
:]
Not Preferred(不推荐):
:)