RayWenderlich 官方 Swift 风格指南

958 阅读21分钟
这是一篇社区协同翻译的文章,已完成翻译,更多信息请点击 协同翻译介绍

已更新到 Swift 3

这篇风格指南可能不同于你看到的其他风格指南。因为它专注于打印和网页的可读性。我们创建这篇风格指南的目的,是为了让我们的书、教程和初学者套件中的代码,在有很多作者同时写书的情况下,也能保持规范与一致。

我们的首要目标是清晰、一致和简洁。

目录

iDev_01 翻译于 2天前 0 重译 Aufree 审阅

正确性

努力让你的代码在没有警告的情况下编译。 这条规则决定了许多风格决策,比如使用 #selector 类型而不是字符串字面量。

命名

描述性和一致性的命名让软件更易于阅读和理解。使用 API 设计原则 中描述的 Swift 命名规范。 一些关键点包括如下:
  • 尽量让调用的地方更加简明
  • 简明性优先而不是简洁性
  • 使用驼峰命名法(而不是蛇形命名法)
  • 针对类型(和协议)使用首字母大写,其它都是首字母小写
  • 包含所有需要的单词,同时省略不必要的单词
  • 基于角色的命名,而不是类型
  • 有时候要针对弱引用类型信息进行补充
  • 尽量保持流畅的用法
  • 工厂方法以 make 开头
  • 命名方法的副作用
    • 不可变版本的动词方法要遵循后接 -ed, -ing的规则
    • 可变版本的名词方法要遵循 formX 的规则
    • 布尔类型应该像断言一样读取
    • 描述 这是什么 的协议应该读作名词
    • 描述 一种能力 的协议应该以 -able 或者 -ible 结尾
  • 使用不会让专家惊讶或让初学者迷惑的术语
  • 通常要避免缩写
  • 使用名称的先例
  • 首选方法和属性而不是自由函数
  • 统一向上或向下包装首字母缩略词和首字母
  • 为相同含义的方法提供相同的基本名称
  • 避免返回类型的重载
  • 选择用于文档的好的参数名
  • 为闭包和元组参数设置标签
  • 利用默认参数的优势
qidong 翻译于 2天前 0 重译 Aufree 审阅

文章

在文章中引用方法时,含义明确是至关重要的。尽可能用最简单的形式引用方法。

  1. 写一个不带参数的方法。 举例: 下一步,你需要调用方法 addTarget
  2. 写一个带参数标签的方法。 举例: 下一步,你需要调用方法 addTarget(_:action:)
  3. 写一个带参数标签和类型的完整方法。 举例: 下一步, 你需要调用方法 addTarget(_: Any?, action: Selector?)
用上面的例子使用 UIGestureRecognizer, 1 是明确的,也是首选的。 专家提示: 你可以用 Xcode 的跳转栏来查看带有参数标签的方法。 file

类前缀

Swift 的类是自动被包含在它们的模块分配命名空间的。你不应该加一个类似于 RW 的类前缀。如果不同模块的两个命名冲突,你可以通过在模块名加前缀来消除歧义。无论如何,仅在有可能混淆的情况下指明模块名,因为这种情况应该是比较罕见的。

import SomeModule

let myClass = MyModule.UsefulClass()
iDev_01 翻译于 2天前 0 重译 Aufree 审阅

代理

当创建自定义代理方法的时候,未命名的第一个参数应该是代理源。 ( 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)
iDev_01 翻译于 2天前 0 重译 Aufree 审阅

一般的

一般的类型参数应该使用描述性的、大写驼峰式的命名。当一个类名没有一个有意义的关系或角色时,使用传统的单个大写字母来命名,例如 TUV推荐:
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: - 注释,以保证代码的结构清晰。

iDev_01 翻译于 2天前 0 重译 Aufree 审阅

协议遵循

推荐为协议方法加一个单独的扩展,尤其是为一个模型加入协议遵循的时候。这可以让有关联的协议方法被分组在一起,也可以简化用类关联方法向这个类添加协议的指令。

推荐:
class MyViewController: UIViewController {
  // 类填充在这
}

// MARK: - UITableViewDataSource
extension MyViewController: UITableViewDataSource {
  // table view 的数据源方法
}

// MARK: - UIScrollViewDelegate
extension MyViewController: UIScrollViewDelegate {
  // scroll view 的代理方法
}
不推荐:
class MyViewController: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
  // 所有方法
}

因为编译器不允许在派生类中重新声明协议遵循,所以并不总是需要复制基类的扩展组。如果派生类是一个终端类且只有少数方法会被重写,则这点在此处尤为准确。何时保留扩展组应该由作者自行决定。

对于 UIKit 中的视图控制器,可考虑将生命周期、自定义存取器和 IBAction 分组在单独的类扩展中。

iDev_01 翻译于 2天前 0 重译 Aufree 审阅

无用代码

无用代码(僵尸代码),包括 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

空格

  • 用两个字符缩进比用制表符缩进更节省空间,同时能防止换行。务必在 Xcode 和项目中设置这个偏好,如下所示:
file
  • 方法大括号和其他大括号( if / else / switch / while 等)总是在和语句相同的行写左括号,而在新行写右括号。
  • 提示:你可以通过选中一些代码(或按 ⌘A 选中全部)然后按 Control-I (或在目录中选择编辑器 -> 结构 -> 重新缩进)的方式来重新缩进代码。一些 Xcode 模板代码会使用 4 个空格的制表符硬编码,这就是一个修正它的好方法。
iDev_01 翻译于 2天前 0 重译 Aufree 审阅 推荐:
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]
}
  • 长行应该在 70 个字符左右被换行(这里并非硬性限制,可自行调整)。
  • 避免在行结尾的地方附上空白。
  • 在每个文件的结尾处增加一个单独的换行符。
iDev_01 翻译于 2天前 0 重译 Aufree 审阅

注释

需要的时候,用注释来解释一个特定的代码片段 为什么 做某件事。注释应保持要么是最新的,要么就被删除。 为了避免块注释和代码内联,代码应该尽可能自文档化。 例外:这不含那些注释被用于生成文档的情况 。

类和结构体

使用哪个?

请记住,结构体有 值语义。对没有标识的事物应用结构体。一个包含 [a, b, c] 的数组和另一个包含 [a, b, c] 的数组是完全一样的。他们是可以完全互换的。使用第一个数组还是第二个数组都无所谓,因为他们代表着完全相同的事物。这就是为什么数组是结构体。 类有 引用语义。对有标识或有具体生命周期的事物应用类。你需要将人建模为一个类,因为不同两个人对象是两个不同的事物。只是因为两个人拥有相同的名字和生日不意味着他们是同一个人。但是人的生日应该是一个结构体,因为 1950 年 3 月 3 日和任何其它的 1950 年 3 月 3 日日期对象是相同的。日期本身没有标识。

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

iDev_01 翻译于 1天前 0 重译 Aufree 审阅

定义的举例

这是一个风格良好的类定义例子:

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: IntCircle: Shape
  • 如果多个变量和结构体共享一个共同的目的 / 上下文,则可以在同一行中定义。
  • 缩进 getter、setter 的定义和属性观察器。
  • 不要再添加如 internal 的默认修饰符。类似的,当重写一个方法时,不要再重复添加访问修饰符。
  • 在扩展中组织额外功能(例如打印)。
  • 隐藏非共享的实现细节,例如 centerString 在扩展中使用 private 访问控制。
iDev_01 翻译于 1天前 0 重译 Aufree 审阅

Self 的使用

为了简洁,避免使用 self 因为 Swift 不需要它来访问一个对象属性或调用它的方法。 仅在被编译器需要时(在 @escaping 闭包或初始化函数中消除参数和属性的歧义)。换句话说,如果不使用 self 可以编译,那么忽略它。

计算属性

为了简洁,如果一个计算属性是只读的,忽略 get 方法。仅在提供了 set 方法的情况下需要 get 方法。

推荐:
var diameter: Double {
  return radius * 2
}
不推荐:
var diameter: Double {
  get {
    return radius * 2
  }
}
iDev_01 翻译于 1天前 0 重译 Aufree 审阅

Final

在教程中将类或成员标记为 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}
iDev_01 翻译于 1天前 0 重译 Aufree 审阅

类型

尽可能的去使用 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

Note: 使用无大小写的枚举的优势就是它不会被意外的实例化,只是单纯的作为一个命名空间。

不推荐:

let e = 2.718281828459045235360287  // 污染全局命名空间
let root2 = 1.41421356237309504880168872

let hypotenuse = side * root2 // what is root2?
qidong 翻译于 1天前 0 重译 Aufree 审阅

静态方法和可变类型属性

静态方法和类型属性对于全局函数和全局变量工作方式类似,应当谨慎使用。当功能的作用域为一个特定函数或需要与 Objective-C 交互时,它们非常有用。

可选类型

在可接受 nil 值的情况下,使用 ? 声明变量和函数返回类型为可选类型。 用 ! 声明的隐式解包类型仅仅用于你知道将在稍后使用之前实初始化的实例变量,比如子视图将在 viewDidLoad 中建立。

当访问一个可选值时,如果值仅被访问一次或有很多可选项值时,使用可选链:

self.textContainer?.textLabel?.setNeedsDisplay()

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

if let textContainer = self.textContainer {
  // 用 textContainer 做很多事情
}
当命名可选变量和属性时,避免将它们命名为 optionalStringmaybeView 这样,因为他们的可选值已经在类型声明中了。 对于可选绑定,适当时使用原始名称而不是使用像 unwrappedViewactualLabel 的名称。

推荐:

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 做某件事
  }
}
iDev_01 翻译于 1天前 0 重译 Aufree 审阅

延迟初始化

在更细粒度地控制对象声明周期时考虑使用延迟初始化。 对于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] 在这里是不需要的。因为保持周期没有被创建。
  • 位置管理器对弹出 UI 向用户申请权限有副作用,所以细颗粒地控制是有意义的。

类型推断

首选简洁的代码,让编译器为单个实例的常量或变量推断类型。类型推断也适合于小(非空)的数组和字典。当需要时,指明特定的类型比如 CGFloatInt16推荐:
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]()
iDev_01 翻译于 1天前 0 重译 Aufree 审阅

空数组和空字典的类型注释

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

推荐:
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>

函数 vs 方法

不附属于类或类型的自有函数应该被谨慎使用。可能的话,首选方法而不是自由函数。这有助于可读性和易领悟性。

自由函数最适用于它们与任何特定类或实例无关的情况。

推荐:
let sorted = items.mergeSorted()  // 容易领悟的
rocket.launch()  // 模型的行为
不推荐:
let sorted = mergeSort(items)  // 难以领悟
launch(&rocket)
自由函数异常
let tuples = zip(a, b)  // 作为自由函数感到自然(对称)
let value = max(x, y, z)  // 另一个感到自然的自由函数
iDev_01 翻译于 20小时前 0 重译 Aufree 审阅

内存管理

代码 (甚至非生产、教程演示代码)不应该创建引用周期。分析你的对象图并用 weakunowned 引用来防止强循环引用。或者,使用值类型( structenum )来彻底防止循环引用。

延长对象的生命周期

使用惯用语法 [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)
}

访问控制

在教程中完整的访问控制注释会分散主题的注意力且是不必要的。然而,适时地使用 privatefileprivate 会增加清晰度也会有助于封装。 在可能的情况下,private 优于 fileprivate。 使用扩展可能要求你使用 fileprivate

当你要求一个完整的访问控制规范时,仅仅明确使用 openpublicinternal

将访问控制用作前置属性说明符。仅有 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()
}
iDev_01 翻译于 20小时前 0 重译 Aufree 审阅

控制流

优先选择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
  }
}
当用 guardif 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")
}
iDev_01 翻译于 19小时前 0 重译 Aufree 审阅

失败防护

对于用某些方法退出,防护语句是必要的。一般地,它应该是简洁的一行语句,比如: returnthrowbreakcontinuefatalError()。应该避免大的代码块。如果对于不同的退出点清理代码是必要的,考虑使用 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")
iDev_01 翻译于 19小时前 0 重译 Aufree 审阅

组织和包 ID

涉及到 Xcode 项目的地方,组织应该被设置为 Ray Wenderlich 并且 包 ID 应该被设置为 com.razeware.TutorialName ,其中 TutorialName 是教程项目的名字。 file

版权声明

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

/// 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艺术表述的最大的笑容。右括号 ) 表示三心二意的笑脸,因此不被推荐。 推荐:
:]
不推荐:
:)

参考文献

iDev_01 翻译于 19小时前 0 重译 Aufree 审阅
原文地址:github.com/raywenderli… 译文地址:ioscaff.com/topics/84/r…

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。