该文章由 iOSCaff 社区 组织翻译,后续社区会保持文章的更新,若是你以为这篇文章对你有帮助,欢迎到社区点赞支持。javascript
译文地址:https://ioscaff.com/topics/84/raywenderlich-official-swift-style-guidehtml
原文地址:https://github.com/raywenderlich/swift-style-guidejava
这篇风格指南可能不一样于你看到的其余风格指南。由于它的重点偏向于打印和网页的可读性。咱们建立这篇风格指南的目的,是为了让咱们的书、教程和初学者套件中的代码,在有不少做者同时写书的状况下,也能保持规范与一致。ios
咱们的首要目标是清晰、一致和简洁。git
努力让你的代码在没有警告的状况下编译。 这条规则决定了许多风格决策,好比使用 #selector
类型而不是字符串字面量。github
描述性和一致性的命名让软件更易于阅读和理解。使用 API 设计规范 中描述的 Swift 命名规范。 一些关键点包括以下:express
make
开头在文章中引用方法时,含义明确是相当重要的。尽量用最简单的形式引用方法。编程
addTarget
。addTarget(_:action:)
。addTarget(_: Any?, action: Selector?)
。用上面的例子使用 UIGestureRecognizer
, 1 是明确的,也是首选的。swift
专家提示: 你能够用 Xcode 的跳转栏来查看带有参数标签的方法。api
Swift 的类自动被包含在模块分配的命名空间中。不该该再添加相似于 RW 的类前缀。若是不一样模块的两个命名冲突,能够在类名前添加模块名来消除歧义。不管如何,仅在少数可能引发混淆的状况下指明模块名。
import SomeModule
let myClass = MyModule.UsefulClass()
复制代码
当建立自定义代理方法的时候,未命名的第一个参数应该是代理源。 ( UIKit 包含不少这样的例子。)
推荐:
func namePickerView(_ namePickerView: NamePickerView, didSelectName name: String)
func namePickerViewShouldReload(_ namePickerView: NamePickerView) -> Bool
复制代码
不推荐:
func didSelectName(namePicker: NamePickerViewController, name: String)
func namePickerShouldReload() -> Bool
复制代码
使用上下文推断编译器书写更短更明确的代码。(你也能够阅读 类型推断。)
推荐:
let selector = #selector(viewDidLoad)
view.backgroundColor = .red
let toView = context.view(forKey: .to)
let view = UIView(frame: .zero)
复制代码
不推荐:
let selector = #selector(ViewController.viewDidLoad)
view.backgroundColor = UIColor.red
let toView = context.view(forKey: UITransitionContextViewKey.to)
let view = UIView(frame: CGRect.zero)
复制代码
通常的类型参数应该是描述性的、大写驼峰法命名。当类名没有富有含义的关系或角色时,使用传统的单个大写字母来命名,例如 T
、 U
或 V
。
推荐:
struct Stack<Element> { ... }
func write<Target: OutputStream>(to target: inout Target)
func swap<T>(_ a: inout T, _ b: inout T)
复制代码
不推荐:
struct Stack<T> { ... }
func write<target: OutputStream>(to target: inout target)
func swap<Thing>(_ a: inout Thing, _ b: inout Thing)
复制代码
使用美式英语拼写来匹配 Apple 的 API。
推荐:
let color = "red"
复制代码
不推荐:
let colour = "red"
复制代码
用扩展将代码组织为功能逻辑块。每一个扩展都应该添加 // MARK: -
注释,以保证代码的结构清晰。
推荐为协议方法加一个单独的扩展,尤为是为一个模型加入协议遵循的时候。这可让有关联的协议方法被分组在一块儿,也能够简化用类关联方法向这个类添加协议的指令。
推荐:
class MyViewController: UIViewController {
// 类填充在这
}
// MARK: - UITableViewDataSource
extension MyViewController: UITableViewDataSource {
// table view 的数据源方法
}
// MARK: - UIScrollViewDelegate
extension MyViewController: UIScrollViewDelegate {
// scroll view 的代理方法
}
复制代码
不推荐:
class MyViewController: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
// 全部方法
}
复制代码
由于编译器不容许在派生类中从新声明协议遵循,因此并不老是须要复制基类的扩展组。若是派生类是一个终端类,而且只有少数方法会被覆盖,那么这个原则尤其正确。应由做者自行决定什么时候保留扩展组- 。
对于 UIKit 中的视图控制器,可考虑将生命周期、自定义存取器和 IBAction 分组在单独的类扩展中。
无用代码(僵尸代码),包括 Xcode 模板代码和占位注释,应该被移除掉。教程或书籍中教用户使用的注释代码除外。
仅实现简单调用父类,但与教程无直接关联的方法应该被移除。这里包括任何为空的或无用的 UIApplicationDelegate 方法。
推荐:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Database.contacts.count
}
复制代码
不推荐:
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// 任何能够重建资源的处理。
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning 未完成的实现,返回节数。
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning 未完成的实现,返回行数。
return Database.contacts.count
}
复制代码
引用最小化。举个例子,引用 Foundation
就足够的状况下不要再引用 UIKit
。
if
/ else
/ switch
/ while
等)老是在和语句相同的行写左括号,而在新行写右括号。if user.isHappy {
// 作一件事
} else {
// 作另外一件事
}
复制代码
不推荐:
if user.isHappy
{
// 作一件事
}
else {
// 作另外一件事
}
复制代码
? :
、空字典 [:]
和带有未命名参数 (_:)
的 #selector
语法 .推荐:
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]
}
复制代码
须要的时候,用注释来解释一个特定的代码片断 为何 作某件事。注释应保持要么是最新的,要么就被删除。
为了不块注释和代码内联,代码应该尽量自文档化。 例外:这不含那些注释被用于生成文档的状况 。
请记住,结构体有 值语义。对没有标识的事物应用结构体。一个包含 [a, b, c] 的数组和另外一个包含 [a, b, c] 的数组是彻底同样的。他们是能够彻底互换的。使用第一个数组仍是第二个数组都无所谓,由于他们表明着彻底相同的事物。这就是为何数组是结构体。
类有 引用语义。对有标识或有具体生命周期的事物应用类。你须要将人建模为一个类,由于不一样两我的对象是两个不一样的事物。只是由于两我的拥有相同的名字和生日不意味着他们是同一我的。可是人的生日应该是一个结构体,由于 1950 年 3 月 3 日和任何其它的 1950 年 3 月 3 日日期对象是相同的。日期自己没有标识。
有时,事物应该是结构体但须要遵循 AnyObject
,或在历史上已经被建模为类 (NSDate
、 NSSet
)。尽量尝试遵循这些原则。
这是一个风格良好的类定义例子:
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)
}
override func area() -> Double {
return Double.pi * radius * radius
}
}
extension Circle: CustomStringConvertible {
var description: String {
return "center = \(centerString) area = \(area())"
}
private var centerString: String {
return "(\(x),\(y))"
}
}
复制代码
上面的例子遵循了如下风格规范:
x: Int
和 Circle: Shape
。internal
的默认修饰符。相似的,当重写一个方法时,不要再重复添加访问修饰符。centerString
在扩展中使用 private
访问控制。为了简洁,请避免使用 self
关键词,Swift 不须要用它来访问一个对象属性或调用它的方法。
仅在编译器须要时(在 @escaping
闭包或初始化函数中,消除参数与属性的歧义)才使用 self。换句话说,若是不须要 self
就能编译经过,则能够忽略它。
为了简洁,若是一个计算属性是只读的,则能够忽略 get 子句。仅在提供了 set 子句的状况下才须要 get 子句。
推荐:
var diameter: Double {
return radius * 2
}
复制代码
不推荐:
var diameter: Double {
get {
return radius * 2
}
}
复制代码
在教程中将类或成员标记为 final
会从主题分散注意力,并且也不必。 尽管如此,final
的使用有时能够代表你的意图,且值得你这样作。在下面的例子中,Box
有特定的目的,且并不打算在派生类中自定义它。标记为 final
可使它更清晰。
// 用这个 Box 类将任何通常类型转换为引用类型。
final class Box<T> {
let value: T
init(_ value: T) {
self.value = value
}
}
复制代码
在一行中保持较短的方法声明,包括左括号:
func reticulateSplines(spline: [Double]) -> Bool {
// 在这里写网格代码
}
复制代码
对于签名较长的函数,则需在合适的位置换行,而后在后续的行中加一个额外的换行:
func reticulateSplines(spline: [Double], adjustmentFactor: Double, translateConstant: Int, comment: String) -> Bool {
// 在这里写网络代码
}
复制代码
仅在参数列表最后有个单独的闭包表达式参数时,使用尾随闭包语法。给闭包参数定义一个描述性的命名。
推荐:
UIView.animate(withDuration: 1.0) {
self.myView.alpha = 0
}
UIView.animate(withDuration: 1.0, animations: {
self.myView.alpha = 0
}, completion: { finished in
self.myView.removeFromSuperview()
})
复制代码
不推荐:
UIView.animate(withDuration: 1.0, animations: {
self.myView.alpha = 0
})
UIView.animate(withDuration: 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 }.index(of: 90)
let value = numbers
.map {$0 * 2}
.filter {$0 > 50}
.map {$0 + 10}
复制代码
请尽量多的使用 Swift 原生类型。 Swift 提供了 Objective-C 桥接,因此当你须要的时候你仍然可使用全套方法。
推荐:
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
可让你的代码避免太多转换,从而让你的代码更加简洁。
使用 let
关键字来定义常量,使用 var
关键字来定义变量。若是变量的值不会改变,则要使用 let
来代替 var
。
提示: 一个比较好的技巧就是定义全部的东西都使用 let
, 当编译器警告的时候再改成 var
。
你能够在一个类型里面去定义常量而不是在类型的实例变量中去使用类型属性。使用 static let
去声明一个类型属性做为常量。用这种方式声明类型属性比声明全局变量更推荐,由于这种方式更能和实例属性区分开。举例:
推荐:
enum Math {
static let e = 2.718281828459045235360287
static let root2 = 1.41421356237309504880168872
}
let hypotenuse = side * Math.root2
复制代码
注意: 使用无大小写枚举的优点,就是它不会被意外的实例化,而只是单纯的做为一个命名空间。
不推荐:
let e = 2.718281828459045235360287 // 污染全局命名空间
let root2 = 1.41421356237309504880168872
let hypotenuse = side * root2 // 什么 root2?
复制代码
静态方法和类型属性跟全局函数和全局变量的工做原理相似,应当谨慎使用。当功能的做用域是一个特定类型或须要与 Objective-C 交互时,它们很是有用。
在可接受 nil 值的状况下,使用 ?
声明变量和函数返回类型为可选类型。
用 !
声明的隐式解包类型,仅用于稍后在使用前初始化的实例变量,好比将在 viewDidLoad
中建立子视图。
当访问一个可选值时,若是值仅被访问一次或在链中有许多可选项时,使用可选链:
self.textContainer?.textLabel?.setNeedsDisplay()
复制代码
当一次性解包和执行多个操做更方便时,使用可选绑定:
if let textContainer = self.textContainer {
// 用 textContainer 作不少事情
}
复制代码
在命名可选变量和属性时,需避免相似 optionalString
或 maybeView
这样的命名,由于他们的可选性已经体如今类型声明中了。
对于可选绑定,适当时使用原始名称,而不是使用像 unwrappedView
或 actualLabel
这样的名称。
推荐:
var subview: UIView?
var volume: Double?
// later on...
if let subview = subview, let volume = volume {
// 使用展开的 subview 和 volume 作某件事
}
复制代码
不推荐:
var optionalSubview: UIView?
var volume: Double?
if let unwrappedSubview = optionalSubview {
if let realVolume = volume {
// 使用 unwrappedSubview 和 volume 作某件事
}
}
复制代码
在更细粒度地控制对象声明周期时考虑使用延迟初始化。 对于UIViewController
,延迟初始化视图是很是正确的。你也能够直接调用 { }()
的闭包或调用私有工厂方法。例如:
lazy var locationManager: CLLocationManager = self.makeLocationManager()
private func makeLocationManager() -> CLLocationManager {
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.delegate = self
manager.requestAlwaysAuthorization()
return manager
}
复制代码
注意:
[unowned self]
。优先选择简洁紧凑的代码,让编译器为单个实例的常量或变量推断类型。类型推断也适合于小(非空)的数组和字典。须要时,请指明特定类型,如 CGFloat
或 Int16
。
推荐:
let message = "Click the button"
let currentBounds = computeViewBounds()
var names = ["Mic", "Sam", "Christine"]
let maximumWidth: CGFloat = 106.5
复制代码
不推荐:
let message: String = "Click the button"
let currentBounds: CGRect = computeViewBounds()
let names = [String]()
复制代码
为空数组和空字典使用类型注释。(对于分配给大型、多行文字的数组和字典,使用类型注释。)
推荐:
var names: [String] = []
var lookup: [String: Int] = [:]
复制代码
不推荐:
var names = [String]()
var lookup = [String: Int]()
复制代码
注意:遵循此原则意味着选择描述性命名比以前更重要。
推荐使用类型声明简短的版本,而不是完整的泛型语法。
推荐:
var deviceModels: [String]
var employees: [Int: String]
var faxNumber: Int?
复制代码
不推荐:
var deviceModels: Array<String>
var employees: Dictionary<Int, String>
var faxNumber: Optional<Int>
复制代码
不附属于类或类型的自有函数应该被谨慎使用。可能的话,首选方法而不是自由函数。这有助于可读性和易领悟性。
自由函数最适用于它们与任何特定类或实例无关的状况。
推荐:
let sorted = items.mergeSorted() // 容易领悟的
rocket.launch() // 模型的行为
复制代码
不推荐:
let sorted = mergeSort(items) // 难以领悟的
launch(&rocket)
复制代码
自由函数异常
let tuples = zip(a, b) // 做为自由函数感到天然(对称)
let value = max(x, y, z) // 另外一个感到天然的自由函数
复制代码
代码 (甚至非生产环境、教程演示的代码)都不该该出现循环引用。分析你的对象图并用 weak
和 unowned
来防止强循环引用。或者,使用值类型( struct
、enum
)来完全防止循环引用。
使用惯用语法 [weak self]
和 guard let strongSelf = self else { return }
来延长对象的生命周期。 在 self
超出闭包生命周期不明显的地方,[weak self]
更优于 [unowned self]
。 明确地延长生命周期优于可选解包。
推荐:
resource.request().onComplete { [weak self] response in
guard let strongSelf = self else {
return
}
let model = strongSelf.updateModel(response)
strongSelf.updateUI(model)
}
复制代码
不推荐:
// 若是在响应返回前 self 被释放,则可能致使崩溃
resource.request().onComplete { [unowned self] response in
let model = self.updateModel(response)
self.updateUI(model)
}
复制代码
不推荐:
// 内存回收能够发生在更新模型和更新 UI 之间
resource.request().onComplete { [weak self] response in
let model = self?.updateModel(response)
self?.updateUI(model)
}
复制代码
在教程中,完整的访问控制注释会分散主题且是没必要要的。然而,适时地使用 private
和 fileprivate
会使代码更加清晰,也会有助于封装。 在合理状况下,private
要优于 fileprivate
。 使用扩展可能会要求你使用 fileprivate
。
只有须要完整的访问控制规范时,才显式地使用 open
、 public
和 internal
。
将访问控制用做前置属性说明符。仅有 static
说明符或诸如 @IBAction
、 @IBOutlet
和 @discardableResult
的属性应该放在访问控制前面。
推荐:
private let message = "Great Scott!"
class TimeMachine {
fileprivate dynamic lazy var fluxCapacitor = FluxCapacitor()
}
复制代码
不推荐:
fileprivate let message = "Great Scott!"
class TimeMachine {
lazy dynamic fileprivate var fluxCapacitor = FluxCapacitor()
}
复制代码
优先选择for
循环的 for-in
格式而不是 while-condition-increment
格式。
推荐:
for _ in 0..<3 {
print("Hello three times")
}
for (index, person) in attendeeList.enumerated() {
print("\(person) is at position #\(index)")
}
for index in stride(from: 0, to: items.count, by: 2) {
print(index)
}
for index in (0...3).reversed() {
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
}
复制代码
当使用条件语句编码时,代码的左边距应该是 「黄金」或「快乐」的路径。就是不要嵌套 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
}
// 用上下文和输入计算频率
return frequencies
}
复制代码
不推荐:
func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
if let context = context {
if let inputData = inputData {
// 用上下文和输入计算频率
return frequencies
} else {
throw FFTError.noInputData
}
} else {
throw FFTError.noContext
}
}
复制代码
当用 guard
或 if let
解包多个可选值时,在可能的状况下使用最下化复合版本嵌套。举例:
推荐:
guard let number1 = number1,
let number2 = number2,
let number3 = number3 else {
fatalError("impossible")
}
// 用数字作某事
复制代码
不推荐:
if let number1 = number1 {
if let number2 = number2 {
if let number3 = number3 {
// 用数字作某事
} else {
fatalError("impossible")
}
} else {
fatalError("impossible")
}
} else {
fatalError("impossible")
}
复制代码
对于用某些方法退出,防御语句是必要的。通常地,它应该是一行简洁的语句,好比: return
、 throw
、 break
、 continue
和 fatalError()
。应该避免大的代码块。若是清理代码被用在多个退出点,则能够考虑用 defer
块来避免清理代码的重复。
在 Swift 中,每条代码语句后面都不须要加分号。只有在你但愿在一行中结合多条语句,才须要加分号。
不要在用分号分隔的单行中写多条语句。
推荐:
let swift = "not a scripting language"
复制代码
不推荐:
let swift = "not a scripting language";
复制代码
注:Swift 很是不一样于 JavaScript。在 JavaScript 中忽略分号 通常被认为不安全。
条件周围的括号是没必要要的,应该被忽略。
推荐:
if name == "Hello" {
print("World")
}
复制代码
不推荐:
if (name == "Hello") {
print("World")
}
复制代码
在更大的表达式中,可选括号有时可让代码读起来更清晰。
推荐:
let playerMark = (player == current ? "X" : "O")
复制代码
涉及到 Xcode 项目的地方,组织应该被设置为 Ray Wenderlich
而且包 ID 应该被设置为 com.razeware.TutorialName
,其中 TutorialName
是教程项目的名字。
如下版权声明应该被包含在每一个源文件的顶部:
/// Copyright (c) 2018 Razeware LLC
///
/// Permission is hereby granted, free of charge, to any person obtaining a copy
/// of this software and associated documentation files (the "Software"), to deal
/// in the Software without restriction, including without limitation the rights
/// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
/// copies of the Software, and to permit persons to whom the Software is
/// furnished to do so, subject to the following conditions:
///
/// The above copyright notice and this permission notice shall be included in
/// all copies or substantial portions of the Software.
///
/// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
/// distribute, sublicense, create a derivative work, and/or sell copies of the
/// Software in any work that is designed, intended, or marketed for pedagogical or
/// instructional purposes related to programming, coding, application development,
/// or information technology. Permission for such use, copying, modification,
/// merger, publication, distribution, sublicensing, creation of derivative works,
/// or sale is expressly withheld.
///
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
/// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
/// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
/// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
/// THE SOFTWARE.
复制代码
笑脸是网站 raywenderlich.com 很是突出的风格特色!正确使用微笑来表达对编码主题的欢乐与兴奋是很是重要的。使用右方括号 ]
是由于它表明 ASCII 中的最大笑容。右括号 )
表示三心二意的笑脸,所以不推荐使用。
推荐:
:]
复制代码
不推荐:
:)
复制代码
iOSCaff 是一个面向 iOS 开发者的技术知识社区,致力于为开发者提供一个更加高效、便捷的学习环境。