raywenderlich.com的官方Swift样式指南

类前缀

Swift类型由包含它们的模块自动命名,并且您不应添加类前缀(例如RW)。 如果来自不同模块的两个名称冲突,则可以通过在类型名称前添加模块名称来消除歧义。 但是,仅在可能出现混淆的情况下指定模块名称,这种情况很少发生。

 导入SomeModule 
 让myClass = MyModule.UsefulClass() 

代表们

创建自定义委托方法时,未命名的第一个参数应该是委托源。 (UIKit包含许多示例。)

首选:

  func namePickerView(_ namePickerView:NamePickerView,didSelectName名称:字符串) 
func namePickerViewShouldReload(_ namePickerView:NamePickerView)->布尔

不推荐:

  func didSelectName(namePicker:NamePickerViewController,名称:字符串) 
func namePickerShouldReload()->布尔

使用类型推断上下文

使用编译器推断的上下文编写更简短的代码。 (另请参见类型推断。)

首选:

 让选择器= #selector(viewDidLoad) 
view.backgroundColor = .red
让toView = context.view(forKey:.to)
让view = UIView(frame:.zero)

不推荐:

 让选择器= #selector(ViewController.viewDidLoad) 
view.backgroundColor = UIColor.red
让toView = context.view(forKey:UITransitionContextViewKey.to)
让view = UIView(frame:CGRect.zero)

泛型

通用类型参数应为描述性的大写驼峰名称。 当类型名称没有有意义的关系或角色时,请使用传统的单个大写字母,例如TUV

首选:

  struct Stack  {...} 
func write (目标:inout目标)
func swap (_ a:inout T,_ b:inout T)

不推荐:

  struct Stack  {...} 
func write (目标:inout目标)
func swap (_ a:inout事物,_ b:inout事物)

语言

使用美国英语拼写以匹配Apple的API。

首选:

 让颜色=“红色” 

不推荐:

 让颜色=“红色” 

代码组织

使用扩展将代码组织成逻辑功能块。 每个扩展名都应以// MARK: -注释以使事情井井有条。

协议一致性

特别是,在向模型添加协议一致性时,最好为协议方法添加单独的扩展。 这样可以将相关方法与协议分组在一起,并且可以简化将协议添加到具有其相关方法的类的说明。

首选:

 类MyViewController:UIViewController { 
//这里的课程
}
  //标记:-UITableViewDataSource 
扩展MyViewController:UITableViewDataSource {
//表格视图数据源方法
}
  //标记:-UIScrollViewDelegate 
扩展MyViewController:UIScrollViewDelegate {
//滚动视图委托方法
}

不推荐:

 类MyViewController:UIViewController,UITableViewDataSource,UIScrollViewDelegate { 
//所有方法
}

由于编译器不允许您在派生类中重新声明协议一致性,因此不一定总是需要复制基类的扩展组。 如果派生类是终端类,并且重写了少量方法,则尤其如此。 何时保留扩展组由作者决定。

对于UIKit视图控制器,请考虑将生命周期,自定义访问器和IBAction分组到单独的类扩展中。

未使用的代码

应该删除未使用的(无效)代码,包括Xcode模板代码和占位符注释。 当您的教程或书籍指示用户使用带注释的代码时,就是一个例外。

与本教程不直接关联的,仅实现其父类的理想方法也应删除。 这包括任何空的/未使用的UIApplicationDelegate方法。

首选:

 覆盖func tableView(_ tableView:UITableView,numberOfRowsInSection部分:Int)-> Int { 
返回Database.contacts.count
}

不推荐:

 覆盖func didReceiveMemoryWarning(){ 
super.didReceiveMemoryWarning()
//处理所有可以重新创建的资源。
}
 覆盖func numberOfSections(在tableView中:UITableView)-> Int { 
// #warning实现不完整,返回段数
返回1
}
 覆盖func tableView(_ tableView:UITableView,numberOfRowsInSection部分:Int)-> Int { 
// #warning实现不完整,返回行数
返回Database.contacts.count
}

最少进口

尽量减少进口。 例如,在导入Foundation就足够时,不要导入UIKit

间距

  • 缩进使用2个空格而不是制表符来节省空间并有助于防止换行。 确保在Xcode和项目设置中设置此首选项,如下所示:
  • 方法大括号和其他大括号( if / else / switch / while等)始终与语句在同一行上打开,但在新行上关闭。
  • 提示:您可以通过选择一些代码(或使用⌘A选择全部),然后选择Control-I(或菜单中的Editor \ Structure \ Re-Indent)来重新缩进。 一些Xcode模板代码将使用4位制表符进行硬编码,因此这是解决此问题的好方法。

首选:

 如果user.isHappy { 
// 做点什么
}其他{
//做其他事情
}

不推荐:

 如果user.isHappy 
{
// 做点什么
}
其他{
//做其他事情
}
  • 在方法之间应该有一条空白线,以帮助提高视觉清晰度和组织性。 方法中的空格应分隔功能,但是方法中的节过多通常意味着您应重构为多个方法。
  • 冒号总是在左边没有空格,而在右边总是一个空格。 三元运算符是例外? : ? : ,未命名参数(_:)空字典[:]#selector语法。

首选:

 类TestDatabase:数据库{ 
var数据:[字符串:CGFloat] = [“ A”:1.2,“ B”:3.2]
}

不推荐:

 类TestDatabase:数据库{ 
var data:[String:CGFloat] = [“ A”:1.2,“ B”:3.2]
}
  • 长行应以70个左右的字符换行。 没有明确规定硬限制。
  • 避免在行尾拖尾空格。
  • 在每个文件的末尾添加一个换行符。

注释

需要它们时,请使用注释来解释为什么特定的代码段会执行某些操作。 评论必须保持最新或删除。

避免在代码中插入块注释,因为代码应尽可能自我记录。 例外:这不适用于用于生成文档的注释。

类和结构

使用哪一个?

请记住,结构具有值语义。 将结构用于没有身份的事物。 包含[a,b,c]的数组实际上与包含[a,b,c]的另一个数组相同,并且它们是完全可互换的。 无论使用第一个数组还是第二个数组都没有关系,因为它们表示的是完全相同的东西。 这就是为什么数组是结构。

类具有参考语义。 将类用于确实具有标识或特定生命周期的事物。 您将一个人建模为一个类,因为两个人对象是两个不同的事物。 仅仅因为两个人的名字和生日相同,并不意味着他们是同一个人。 但是此人的出生日期将是一个结构,因为1950年3月3日的日期与1950年3月3日的任何其他日期对象相同。该日期本身没有身份。

有时,事物应该是结构,但需要符合AnyObject或历史上已经建模为类( NSDateNSSet )。 尝试尽可能遵循这些准则。

示例定义

这是一个样式良好的类定义的示例:

 圆形:形状{ 
var x:Int,y:Int
var radius:Double
var直径:双{
得到{
返回半径* 2
}
设置{
半径= newValue / 2
}
}
  init(x:Int,y:Int,radius:Double){ 
self.x = x
self.y = y
self.radius =半径
}
 便利init(x:整数,y:整数,直径:两倍){ 
self.init(x:x,y:y,半径:直径/ 2)
}
 覆盖func area()-> Double { 
返回Double.pi *半径*半径
}
}
 扩展圈:CustomStringConvertible { 
var说明:字符串{
返回“中心= \(centerString)区域= \(area())”
}
私人var centerString:字符串{
返回“((\(x),\(y))”
}
}

上面的示例演示了以下样式准则:

  • 为属性,变量,常量,参数声明和其他语句指定类型,在冒号之后但不在空格之前,例如x: IntCircle: Shape
  • 如果它们具有共同的目的/上下文,请在一行上定义多个变量和结构。
  • 缩进getter和setter定义以及属性观察器。
  • 当它们已经是默认值时,请勿添加诸如internal修饰符。 同样,覆盖方法时不要重复访问修饰符。
  • 在扩展中组织额外的功能(例如打印)。
  • 使用private访问控制在扩展内隐藏非共享的实现细节,例如centerString

自我的运用

为简洁起见,请避免使用self因为Swift不需要它访问对象的属性或调用其方法。

仅在编译器需要时使用self(在@escaping闭包中,或在初始化程序@escaping属性与参数区@escaping )。 换句话说,如果它在没有self情况下编译,那么就忽略它。

计算属性

为简洁起见,如果计算的属性为只读,请省略get子句。 仅在提供set子句时才需要get子句。

首选:

  var直径:双{ 
返回半径* 2
}

不推荐:

  var直径:双{ 
得到{
返回半径* 2
}
}

最后

在教程中将班级或成员标记为final班级可能会偏离主要主题,并且不是必需的。 但是,使用final有时可以澄清您的意图,并且值得花费。 在下面的示例中, Box具有特定的用途,并且不希望在派生类中进行自定义。 将其标记为final清晰。

  //使用此Box类将任何泛型类型转换为引用类型。 
最后一课Box {
出租价值:T
init(_值:T){
self.value =值
}
}

功能声明

将简短的函数声明放在一行中,包括左括号:

  func reticulateSplines(spline:[Double])-> Bool { 
//网状代码在这里
}

对于签名较长的函数,请在适当的位置添加换行符,并在后续的行上添加额外的缩进:

  func reticulateSplines(spline:[Double],AdjustmentFactor:Double, 
translateConstant:Int,注释:字符串)-> Bool {
//网状代码在这里
}

闭包表达式

仅当参数列表末尾有单个闭包表达式参数时,才使用尾随闭包语法。 为闭包参数指定描述性名称。

首选:

  UIView.animate(withDuration:1.0){ 
self.myView.alpha = 0
}
  UIView.animate(withDuration:1.0,动画:{ 
self.myView.alpha = 0
},完成:{已完成
self.myView.removeFromSuperview()
})

不推荐:

  UIView.animate(withDuration:1.0,动画:{ 
self.myView.alpha = 0
})
  UIView.animate(withDuration:1.0,动画:{ 
self.myView.alpha = 0
}){f in
self.myView.removeFromSuperview()
}

对于上下文清晰的单表达式闭包,请使用隐式返回:

  AttendeeList.sort {a,b in 
a> b
}

使用结尾闭包的链接方法应清晰易读。 关于间距,换行符以及何时使用命名和匿名参数的决定由作者自行决定。 例子:

 让值=数字.map {$ 0 * 2} .filter {$ 0%3 == 0} .index(of:90) 
 令值=数字 
.map {$ 0 * 2}
.filter {$ 0> 50}
.map {$ 0 + 10}

种类

可用时始终使用Swift的本机类型。 Swift提供了与Objective-C的桥梁,因此您仍然可以根据需要使用全套方法。

首选:

  let width = 120.0 //双 
let widthString =(宽度为NSNumber).stringValue //字符串

不推荐:

 让宽度:NSNumber = 120.0 // NSNumber 
让widthString:NSString = width.stringValue // NSString

在Sprite Kit代码中,如果CGFloat通过避免过多的转换而使代码更简洁,请使用CGFloat

常数

常量使用let关键字定义,变量使用var关键字定义。 如果变量的值不变,请始终使用let而不是var

提示:一项好的技术是使用let定义所有内容,并且只有在编译器抱怨时才将其更改为var

您可以使用类型属性在类型上而不是在该类型的实例上定义常量。 要将类型属性声明为常量,只需使用static let 。 通常,以这种方式声明的类型属性优于全局常量,因为它们更容易与实例属性区分开。 例:

首选:

 枚举数学{ 
静态让e = 2.718281828459045235360287
静态让root2 = 1.41421356237309504880168872
}
 设斜边=边* Math.root2 

注意:使用不区分大小写的枚举的优点是它不会被意外实例化并且可以用作纯名称空间。

不推荐:

  let e = 2.718281828459045235360287 //污染全局名称空间 
让root2 = 1.41421356237309504880168872
 让斜边=边* root2 //什么是root2? 

静态方法和变量类型属性

静态方法和类型属性的工作方式与全局函数和全局变量类似,应谨慎使用。 当功能仅限于特定类型或需要Objective-C互操作性时,它们很有用。

选装件

将变量和函数返回类型声明为可选的? 零值可以接受的地方。

使用用!声明的隐式解包类型! 仅针对您知道的实例变量,这些变量将在使用前稍后初始化,例如将在viewDidLoad设置的子视图。

访问可选值时,如果该值仅被访问一次,或者如果该链中有很多可选项,则使用可选链接:

  self.textContainer?.textLabel?.setNeedsDisplay() 

当一次解包并执行多个操作更方便时,请使用可选绑定:

 如果让textContainer = self.textContainer { 
//使用textContainer做很多事情
}

在命名可选变量和属性时,请避免将它们命名为optionalStringmaybeView因为其可选性已经在类型声明中。

对于可选绑定,请在适当时actualLabel原始名称,而不要使用诸如actualLabelactualLabel类的名称。

首选:

  var subview:UIView? 
var音量:两倍?
  // 稍后的... 
如果let subview = subview,则让volume = volume {
//使用展开后的子视图和体积执行某些操作
}

不推荐:

  var optionalSubview:UIView? 
var音量:两倍?
 如果让unwrappedSubview = optionalSubview { 
如果让realVolume = volume {
//使用unwrappedSubview和realVolume做一些事情
}
}

延迟初始化

考虑使用延迟初始化来更好地控制对象生命周期。 对于延迟加载视图的UIViewController尤其如此。 您可以使用立即称为{ }()的闭包,也可以调用私有工厂方法。 例:

 懒惰的var locationManager:CLLocationManager = self.makeLocationManager() 
 私人功能makeLocationManager()-> CLLocationManager { 
让经理= CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.delegate =自我
manager.requestAlwaysAuthorization()
退货经理
}

笔记:

  • 这里不需要[unowned self] 。 不会创建保留周期。
  • 位置管理器具有弹出UI征询用户权限的副作用,因此在这里进行精细控制很有意义。

类型推断

首选紧凑代码,让编译器为单个实例的常量或变量推断类型。 类型推断也适用于小型(非空)数组和字典。 如果需要,请指定特定类型,例如CGFloatInt16

首选:

  let message =“单击按钮” 
让currentBounds = computeViewBounds()
变量名称= [“麦克风”,“山姆”,“克里斯汀”]
让maximumWidth:CGFloat = 106.5

不推荐:

 让消息:String =“单击按钮” 
让currentBounds:CGRect = computeViewBounds()
让名称= [String]()

空数组和字典的类型注释

对于空数组和字典,请使用类型注释。 (对于分配给大型多行文字的数组或字典,请使用类型注释。)

首选:

  var名称:[String] = [] 
var查找:[String:Int] = [:]

不推荐:

  var名称= [String]() 
var lookup = [String:Int]()

注意:遵循此准则意味着选择描述性名称比以前更加重要。

句法糖

最好使用类型声明的快捷方式版本,而不要使用完整的泛型语法。

首选:

  var deviceModels:[String] 
var员工:[Int:字符串]
var FaxNumber:整数?

不推荐:

  var deviceModels:Array  
var员工:Dictionary
var FaxNumber:可选

函数与方法

未附加在类或类型上的自由函数应谨慎使用。 如果可能,最好使用方法而不是自由函数。 这有助于提高可读性和可发现性。

自由函数不与任何特定类型或实例相关联时,它们是最合适的。

首选的

 让sorted = items.mergeSorted()//容易发现 
rocket.launch()//作用于模型

不喜欢

 让sorted = mergeSort(items)//很难发现 
发射(火箭)

自由功能异常

  let tuples = zip(a,b)//作为自由函数感觉自然(对称) 
let value = max(x,y,z)//感觉自然的另一个自由函数

内存管理

代码(甚至非生产的教程演示代码)也不应创建参考周期。 分析对象图并使用weak引用和unowned引用来防止出现强循环。 或者,使用值类型( structenum )完全防止循环。

延长对象寿命

使用[weak self]guard let strongSelf = self else { return }来延长对象生命期, guard let strongSelf = self else { return }惯用语。 [weak self][unowned self]更为可取,因为在这种情况下, self并不能比闭包更有效。 显式延长使用寿命比选择可选的展开方法更可取。

首选的

  resource.request()。onComplete {[弱自我]回应 
守卫让strongSelf =自我else {
返回
}
让模型= strongSelf.updateModel(response)
strongSelf.updateUI(模型)
}

不喜欢

  //如果在响应返回之前释放self,则可能会崩溃 
resource.request()。onComplete {
让模型= self.updateModel(响应)
self.updateUI(模型)
}

不喜欢

  //在更新模型和更新UI之间可能会发生解除分配 
resource.request()。onComplete {[弱自我]回应
让模型=自我?.updateModel(响应)
自我?.updateUI(模型)
}

访问控制

教程中的完整访问控制注释可能会偏离主要主题,因此不是必需的。 但是,适当地使用privatefileprivate可以增加清晰度并促进封装。 如果可能, fileprivate使用privatefileprivate 。 使用扩展名可能需要您使用fileprivate

仅在需要完整的访问控制规范时才显式使用openpublicinternal

使用访问控制作为主要的属性说明符。 访问控制之前唯一应做的事情是static说明符或属性,例如@IBAction@IBOutlet@discardableResult

首选:

 私人让步消息=“太棒了!” 
  TimeMachine类{ 
fileprivate动态懒惰var fluxCapacitor = FluxCapacitor()
}

不推荐:

  fileprivate let message =“伟大的斯科特!” 
  TimeMachine类{ 
惰性动态文件专用var fluxCapacitor = FluxCapacitor()
}

控制流

while-condition-increment样式相比, for循环的for-in样式更while-condition-increment

首选:

  for _ in 0 .. <3 { 
打印(“你好三遍”)
}
  for与会者eeList.enumerated()中的(索引,人){ 
print(“ \(person)在位置#\(index)”)
}
 用于大步前进的索引(从:0,到:items.count,通过:2){ 
打印(索引)
}
 用于(0 ... 3).reversed()中的索引{ 
打印(索引)
}

不推荐:

 变量i = 0 
而我<3 {
打印(“你好三遍”)
我+ = 1
}

 变量i = 0 
而我<与会者列表。计数{
让个人=与会者列表[i]
print(“ \(person)在位置#\(i)”)
我+ = 1
}

黄金之路

当使用条件编码时,代码的左边距应为“黄金”或“快乐”路径。 也就是说,不要嵌套if语句。 多个return语句可以。 为此构建了guard声明。

首选:

  funccomputeFFT(context:Context ?, inputData:InputData?)throws->频率{ 
 警卫队让context = context else { 
抛出FFTError.noContext
}
保护卫队inputData = inputData else {
抛出FFTError.noInputData
}
  //使用上下文和输入来计算频率 
 返回频率 
}

不推荐:

  funccomputeFFT(context:Context ?, inputData:InputData?)throws->频率{ 
 如果让context = context { 
如果让inputData = inputData {
//使用上下文和输入来计算频率
 返回频率 
}其他{
抛出FFTError.noInputData
}
}其他{
抛出FFTError.noContext
}
}

如果使用guardif let来解开多个可选项,则在可能的情况下使用复合版本来最大程度地减少嵌套。 例:

首选:

 看守数字1 =数字1, 
令number2 = number2,
让number3 = number3 else {
fatalError(“不可能”)
}
//用数字做某事

不推荐:

 如果让number1 = number1 { 
如果让number2 = number2 {
如果让number3 = number3 {
//用数字做某事
}其他{
fatalError(“不可能”)
}
}其他{
fatalError(“不可能”)
}
}其他{
fatalError(“不可能”)
}

失败守卫

需要以某种方式退出Guard语句。 通常,这应该是简单的一行语句,例如returnthrowbreakfatalError()fatalError() 。 应避免使用较大的代码块。 如果多个出口点需要清除代码,请考虑使用defer块以避免清除代码重复。

分号

在代码中的每个语句之后,Swift不需要分号。 仅当您希望在一行上合并多个语句时才需要使用它们。

不要在用分号分隔的一行上写多个语句。

首选:

 让swift =“不是脚本语言” 

不推荐:

 让swift =“不是脚本语言”; 

注意:Swift与JavaScript有很大的不同,在JavaScript中省略分号通常被认为是不安全的

括号

不需要带条件的括号,应将其省略。

首选:

 如果名称==“你好” { 
打印(“世界”)
}

不推荐:

  if(name ==“ Hello”){ 
打印(“世界”)
}

在较大的表达式中,可选的括号有时可以使代码阅读更清晰。

首选:

 让playerMark =(玩家==当前?“ X”:“ O”) 

组织和捆绑标识符

如果涉及到Xcode项目,则应将组织设置为Ray Wenderlich ,并将Bundle Identifier设置为com.razeware.TutorialName ,其中TutorialName是教程项目的名称。

版权声明

每个源文件的顶部应包含以下版权声明:

  / ** 
*版权所有(c)2017 Razeware LLC
*
*特此授予获得副本的任何人免费的许可
*本软件和相关文档文件(以下简称“软件”)的交易
*在软件中不受限制,包括但不限于权利
*使用,复制,修改,合并,发布,分发,再许可和/或出售
*本软件的副本,并允许本软件所涉及的人
*具备以下条件:
*
*以上版权声明和本许可声明应包含在
*本软件的所有副本或主要部分。
*
*该软件按“原样”提供,不提供任何形式的明示或明示保证
*暗示,包括但不限于对适销性的保证,
*适用于特定目的和非侵权。 在任何情况下都不会
*作者或版权持有人对任何索赔,损害或其他责任
*由于合同,侵权或其他方式的责任,
*与软件或软件的使用或其他处理无关或与之有关
*该软件。
* /

笑脸

笑脸是raywenderlich.com网站上非常突出的风格特征! 拥有正确的笑容表示编码主题非常高兴和兴奋是非常重要的。 之所以使用右方括号[ ]是因为它代表能够使用ASCII艺术作品捕获的最大笑容。 右括号( )会产生一个半心半意的微笑,因此不受欢迎。

首选:

 :] 

不推荐:

 :) 

资料来源:https://github.com/raywenderlich/swift-style-guide