Swift Code Style

前言

本文档是翻译著名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我。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容