前言
本文档是翻译著名raywenderlich Swift Style Guide 并结合自身情况结合生成原文地址
内容目录
<span id = "Correctness">基本准则</span>
将警告视为错误(健壮的工程应该没有一个警告)
<span id = "Naming">命名</span>
使用驼峰命名法命名类, 方法, 变量等等, 类名以及协议名首字母应大写, 然而方法名和变量名首字母小写.常量应以 k 作为首字母
推荐 :
private let kMaximumWidgetCount = 100
class WidgetContainer {
var widgetButton: UIButton
let widgetHeightPercentage = 0.85
}
不推荐 :
let MAX_WIDGET_COUNT = 100
class app_widgetContainer {
var wBut: UIButton
let wHeightPct = 0.85
}
通常应该避免缩略语和首字母缩略词, 缩写词和缩写应统一大写或小写. 例子:
推荐
let urlString: URLString
let userID: UserID
不推荐
let uRLString: UrlString
let userId: UserId
对于函数以及 init 方法, 倾向于对所有的参数命名, 除非上下文非常简洁清晰.如果能使函数更具可读性, 我们也可以包括外部参数名称.
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)
<span id = "Common">常量和变量</span>
1.公开变量:用 Public 修饰, 如果这个变量是必传参数, 应该用 ! 标明, 如果是可选参数, 用 ? 标明
2.私有变量:用 private 修饰.
<span id = "Enumerations">枚举</span>
根据Swift3苹果代码规范,枚举名称首字母应该大写,枚举值使用小驼峰. 例如:
enum Shape {
case rectangle
case square
case rightTriangle
case equilateralTriangle
}
OC命名同使用大写驼峰命名法, 而枚举值必须以枚举名开头, 便于与 Swift 混编
typedef NS_ENUM(NSUInteger, OKIShape) {
case OKIShareRectangle
case OKIShareSquare
case OKIShareTriangle
case OKIShareCircle
}
<span id = "Class-Prefixes">类前缀</span>
按理说Swift不应该加入类前缀,但是为了和OC规范一致,在此项目类前缀为 OK
+ 项目名称的首字母
<span id = "Generics">泛型</span>
泛型类型参数应具有描述性,大写驼峰型的名字. 在类型名称时,没有一个有意义的关系或角色,请使用传统的单一的大写字母,例如T U或v
推荐:
struct Stack<Element> { ... }
func writeTo<Target: OutputStream>(inout target: Target)
func max<T: Comparable>(x: T, _ y: T) -> T
不推荐:
struct Stack<T> { ... }
func writeTo<target: OutputStream>(inout t: target)
func max<Thing: Comparable>(x: Thing, _ y: Thing) -> Thing
<span id = "Language">使用国际英语而不是美式英语</span>
推荐:
let color = "red"
不推荐:
let colour = "red"
<span id = "Code-Organization">****代码结构****</span>
使用扩展将代码组织成逻辑功能的块. 每个扩展应设置一个注释:// MARK: -
保持良好的注释.
<span id = "Protocol">协议一致性</span>
在 Swift 中, 推荐使用 extension 的方式来分割实现的代码, 在视觉上可以更好的加强用户的阅读体验, 当然, 别忘了加上 //MARK: -
推荐:
class MyViewcontroller: UIViewController {
// class stuff here
}
// MARK: - 如果这个 extension 需要说明, 可以这样注释在这里
extension MyViewcontroller: UITableViewDataSource {
// table view data source methods
}
// MARK: - UIScrollViewDelegate
extension MyViewcontroller: UIScrollViewDelegate {
// scroll view delegate methods
}
不推荐:
class MyViewcontroller: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
// all methods
}
<span id = "Unused-Code">无用代码</span>
要将文件中无用的代码和注释删除掉,这样保持界面整洁
不推荐:
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
}
推荐:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Database.contacts.count
}
<span id = "Spacing">空格</span>
- 使用空格而不使用 Tab,永远使用4个空格来表示换行, 而不要使用 Tab 来换行.
- 你可以设置你的 IDE 自动将输入的 Tab 转换为空格.
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]
}
<span id = "Word-Limit">每行的长度不应该超过120</span>
每行的长度如果过长了话会影响阅读代码的体验, 比如有的开发者习惯左边和右边的 pannel 同时打开, 那么120个字符/行的长度是最适宜的, 这样可以保证在开关左右 pannel 的时候 IDE 不会出现换行, 你可以在 Xcode 中设置每行的最大长度的警告线 (Preferences > Text Editing > Page guide at column: 120)
<span id = "Line">不要使用 C 语言的括号形式 空格和换行</span>
条件控制语句, 例如 if/else/while/switch 等的左括号应当在本行, 而右括号应当在新的一行.
推荐:
if user.isHappy {
// Do something
} else {
// Do something else
}
不推荐:
if user.isHappy
{
// Do something
}
else {
// Do something else
}
如果方法名过长需要换行了话, 在第二行空4格
当一个函数超过120的时候, 可以在适当的地方开始换行(如某个参数的结尾)
推荐:
func reticulateSplines(spline: [Double], adjustmentFactor: Double,
translateConstant: Int, comment: String) -> Bool {
// reticulate code goes here
}
<span id = "Comments">注释</span>
首先, 虽然注释很重要,但是最好的注释就是自文档化.注释是用来解释说明某段特定的代码做了些什么, 或者为什么这么做.同时,注释必须能否保持更新或删除.例外:这并不适用于那些用来生成文档的注释.
<span id = "Declaration-Comments">类的声明注释</span>
每一个class、category或protocol都需要一个注释说明其作用及如何使用.
注释要包括概述、必要参数、版本信息、作者、更新信息, 如:
/// 说明该类的作用, 及如何使用
///
/// 参数: 必要的参数
///
/// @since 版本信息
/// @author 更新作者 更新信息
另外, 如果是public的方法, 要对每个方法做注释, 包括参数、返回值以及注意情况等
实现中的注释
用竖线`` 代替引号“”, 这样能消除一些歧义, 如:
// Sometimes we need `count` to be less than zero.
或者本身带有引号的情况, 如:
// Remember to call `StringWithoutSpaces("foo bar baz")`
不要在注释中嵌入代码片段
不要在注释中嵌入代码片段, 因为代码要尽可能的自文档化, 并且不利于自动生成文档
<span id = "Function-Comments">对方法的描述以及引用</span>
当涉及方法并包括所需的参数名称时, 应该从调用者的角度命名, 或使用_命名为缺省.
从你自己创建的init方法调用调用 convertPointAt(column:row:) .
如果你调用dateFromString(_:)要确定你提供了一个格式为"yyyy-MM-dd"的字符串.
如果你从 viewDidLoad()调用方法 timedAction(afterDelay:perform:) 记得提供一个调整延迟值和一个动作来执行此方法.
你不应该直接调用数据源方法: tableView(_:cellForRowAtIndexPath:) .
如果有疑问, 可以参照 Xcode 在跳转栏的方法, 风格和它保持一致.
<span id = "Class-Struct">类和结构体</span>
<span id = "Instruct">何时选用 Class, 何时选用 Struct?</span>
在 Swift 中, Class 是引用类型, Struct 是值类型, 值类型有许多好处, 例如当你传递一个值类型给另一个对象是, 发生的是深拷贝(deep copy)操作, 不会出现改了后者就改了前者的问题, 还意味着线程安全, 虽然很有可能有人会说值类型传递是相对耗时的(的确, 相对引用类型, 传递值类型是会稍微耗时一点), 但是, 我们应当认为在如今的世界中, 这种不是复杂度提升级别的耗时(耗时为一个常量)相对于值类型能带来的好处是非常明显的, 所以我们建议在 Swift 中请优先考虑使用值类型. (关于性能问题如果你深陷网络"性能陷阱"不能自拔, 可以查阅 WWDC 2015 - Swift Value Type 来看看苹果官方的回答, 更可以遵循我组第二原则 : 没有真正看到性能问题时不要考虑性能问题), 下面我们就创建对象时的选用 Class 的情况做一个简单的描述:
1.当一个对象具有唯一性的时候选用 Class:
如果你要创建的对象是一个具有 identifer 的对象, 例如 User, Task, 哪怕两个 User 具有相同的名字, 但是不同的 UID 即表明他们不是同一个人, 那么这个时候 Class 更加合适, 但是比如这个 User.Birthday 可能是一个 Date(你自定义的 Date), 这个 Date 就更适合用 Struct, 因为两个用户如果都出生在10月1日, 那么这个10月1日是对等的.
2.不要和系统的 API 对着干:
如果系统的 API 要你传一个 Class, 那么就传一个 Class, 不要想方设法的对抗系统(尤其是使用只有你看得懂的黑魔法).
除非语法需要, 否则不要显式使用 self
在 Swift 中, 默认调用方法或者 property 的时候不需要使用 self.xxx 来调用, 另外, Swift 要求闭包(Closure)中使用 self.xxx 来显式的暗示 self 已经被 retain 了, 所以, 在正常的方法调用以及 property 的调用中, 不要使用 self.xxx 来调用以免产生歧义.
class BoardLocation {
let row: Int, column: Int
init(row: Int, column: Int) {
self.row = row
self.column = column
let closure = {
print(self.row)
}
}
}
<span id = "Computed-Property">计算型属性 (Computed Property)</span>
如果一个 Computed Property 是只读的, 则不要写 get:
推荐:
var diameter: Double {
return radius * 2
}
不推荐:
var diameter: Double {
get {
return radius * 2
}
}
<span id = "Final">Final</span>
当自定义类不允许被继承时,应该用final
标记这个类.例如:
// Box这个类不允许被继承
final class Box<T> {
let value: T
init(_ value: T) {
self.value = value
}
}
<span id = "Closures">闭包(Closures)</span>
尽可能使用尾随闭包语法. 在所有情况下, 闭包的参数需要有一个描述性的名称.
闭包中要注意循环引用情况,使用[weak self]解决此问题
推荐:
UIView.animateWithDuration(1.0) {
self.myView.alpha = 0
}
UIView.animateWithDuration(1.0,
animations: {
self.myView.alpha = 0
},
completion: { finished in
self.myView.removeFromSuperview()
}
)
不推荐:
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
}
<span id = "Types">类型 (Types)</span>
尽量使用 Swift 的原生类型, Swift 可以通过桥接文件对 OC 进行调用. 对一个属性进行声明时, 倾向于同时声明属性的类型.
推荐:
let width = 120.0 // Double
let widthString = (width as NSNumber).stringValue // String
不推荐:
let width: NSNumber = 120.0 // NSNumber
let widthString: NSString = width.stringValue // NSString
在Sprite Kit代码中, 使用CGFloat, 如果它使代码更简洁, 避免过多的转换
<span id = "Constants">常量 (Constants)</span>
使用 let 定义常量, var 定义变量. 如果一个变量不会发生变化, 尽量用 let 修饰而不是 var.
对于 Class 应当都使用 let 而不是 var, 因为 Class 是引用类型, var 和 let 的修饰符并不可以阻止其属性被修改
提示:一个好的技巧是用 let 定义一切属性, 只有当编译器提示时才改为 var.
<span id = "Type-Inference">类型推断 (Type Inference)</span>
虽然通过编译器的类型推断使代码更加紧凑, 但是, 这意味着要求你的变量声明的可读性要比之前更高.
所以, 尽量给常量或变量声明带上确切的类型.
推荐:
let maximumWidth: CGFloat = 106.5
不推荐:
let maximumWidth = 106.5
<span id = "Optionals">可选类型 (Optionals)</span>
- 在nil值可能出现的情况下, 将变量跟函数返回值的类型通过?定义成Optional.
- 只有在确定实例变量会在初始化之后才被使用的情况下, 通过 ! 将其定义为隐式解包类型(Implicitly Unwrapped Types),
- 比如说会在viewDidLoad中被创建的子视图. 在访问一个Optional值时, 如果该值只被访问一次, 或者之后需要连续访问多个Optional值, 请使用链式Optional语法:
self.textContainer?.textLabel?.setNeedsDisplay()
if let 判断可选类型一次, 可以在作用域内做多个操作
if let textContainer = self.textContainer {
// do many things with textContainer
}
- 当命名可选变量或属性, 避免命名为比如:optionalString 或者 maybeView,因为他们的可选性已经在类型声明时明确了.
- if let 判断语句尽量使解包前后变量名一致.
推荐:
var subview: UIView?
var volume: Double?
// later on...
if let subview = subview, 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
}
}
不要使用强转符号(!)将一个 nullable 的对象转换为 nonnull 的对象, 绝大多数的崩溃错误都是由此引起的
相对于if let 我们更偏好使用 guard let, 因为 guard 语法会强制你实现 else 的逻辑, 让错误的 handle 更加优雅
<span id = "Struct-Initializers">初始化结构体 (Struct Initializers)</span>
尽量使用新的 Swift 的 API 而不是 Objective-C 的 bridge 后的 API:
推荐:
let bounds = CGRect(x: 40, y: 20, width: 120, height: 80)
let centerPoint = CGPoint(x: 96, y: 42)
不推荐:
let bounds = CGRectMake(40, 20, 120, 80)
let centerPoint = CGPointMake(96, 42)
推荐使用 Swift 形式的常量: Module.Constant, 而不是 C 形式的常量: ModuleConstant:
推荐:
CGRect.infinite, CGRect.null
不推荐:
CGRectInfinite, CGRectNull
<span id = "Lazy-Initialization">懒加载 (Lazy Initialization)</span>
使用延迟初始化的精细控制对象的生存期.对于延迟加载视图的视图控制器
style1.
private lazy var locationManager: CLLocationManager = CLLocationManager()
style2.
private lazy var locationManager: CLLocationManager = {
let locationManager = CLLocationManager()
return locationManager
}()
style3.
lazy var locationManager: CLLocationManager = self.makeLocationManager()
private func makeLocationManager() -> CLLocationManager {
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.delegate = self
manager.requestAlwaysAuthorization()
return manager
}
空数组和字典的类型批注 (Type Annotation for Empty Arrays and Dictionaries)
空数组和字典, 使用类型批注. 对指定的数组或字典, 多行文本, 请使用类型批注.
推荐:
var names: [String] = []
var lookup: [String: Int] = [:]
不推荐:
var names = [String]()
var lookup = [String: Int]()
<span id = "Syntactic-Sugar">语法糖 (Syntactic Sugar)</span>
使用类型定义个快捷语法而不要使用完整的语法
推荐:
var deviceModels: [String]
var employees: [Int: String]
var faxNumber: Int?
不推荐:
var deviceModels: Array<String>
var employees: Dictionary<Int, String>
var faxNumber: Optional<Int>
<span id = "Memory-Management">内存管理 (Memory Management)</span>
推荐:
resource.request().onComplete { [weak self] response in
guard let strongSelf = self else { return }
let model = strongSelf.updateModel(response)
strongSelf.updateUI(model)
}
不推荐:
// 如果self在response返回之前就被释放了 可能会引发崩溃
resource.request().onComplete { [unowned self] response in
let model = self.updateModel(response)
self.updateUI(model)
}
不推荐:
resource.request().onComplete { [weak self] response in
let model = self?.updateModel(response)
self?.updateUI(model)
}
<span id = "Access-Control">流程控制 (Access Control)</span>
<span id = "Control-Flow">控制流 (Control Flow)</span>
遍历请使用for-in的格式, 而不是C语言风格的for-condition-increment的格式
推荐:
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)
}
不推荐:
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
}
<span id = "Golden-Path">Golden Path</span>
善用guard let 而不是嵌套if let 来做多个条件判断
推荐:
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 let来减少嵌套.例如:
推荐:
guard let number1 = number1, number2 = number2, 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")
}
<span id = "Semicolons">分号 (Semicolons)</span>
- Swift在任何语句后都不需要分号, 分号仅在你将多个语句写在一行时有用(用于区分语句)
- 不要将多个语句写在一行然后用分号区分
这个规则的唯一例外是for语句, 它需要分号. 然而, 在可能的情况下尽量使用可选的 for-in 语句
推荐:
let swift = "not a scripting language"
不推荐:
let swift = "not a scripting language";
## <span id = "Parentheses">****圆括号**** (Parentheses)</span>
不需要括号条件, 应予以删除.
推荐:
if name == "Hello" {
print("World")
}
不推荐:
if (name == "Hello") {
print("World")
}
总结
无规矩不成方圆,愿这篇文章能够帮助到大家。本人会不定时更新,如有异议,请issue我。