Swift 设计指南之 编程规范

基本准则

  • 用法一目了然是你设计时最重要的目的。

方法和属性这样的实体只声明一次,却会被重复调用。因此你在设计 API 时应尽可能使其简单明了。当评估某个设计时,只阅读声明往往是不够的,有时还需要检查它的使用样例,才能确保其在上下文中足够清晰。

  • 一目了然比简洁更重要。 尽管 Swift 代码可以非常简明,但是使用少量的字符使得代码变得简短并不是我们的目的。简洁的 Swift 代码,会成为强类型系统副作用,而同时也是自然地降低版面的重要特点。
  • 给每个声明编写文档注释。编写文档会对你的设计产生深远的影响进而增加你的见解,所以不要闲置它们。

??如果你不能简单的描述 API 功能,那么你很有可能设计错了 API。

  • 使用 Swift 形式地 Markdown —— Markup
  • 描述实体行为声明的摘要开始。通常,API 应该能够通过它的声明和摘要完全理解。
/// 返回 `self` 的  "view",其中包含顺序相反的相同元素。func reversed() -> ReverseCollection
  • 专注于摘要;这是最重要的部分。很多杰出的文档其实什么都没有,但是它们有很好的注释摘要。
  • 如果可以,使用单一的语句片段并用句号结尾。不要写一个复杂的句子。
  • 要描述一个函数或方法会干什么,以及会返回什么,省略那些没有意义的以及返回Void的说明。
/// 在 `self` 的起始处插入 `newHead` 。mutating func prepend(newHead: Int)

/// 返回一个由 `head` 和 `self` 依次组成的 `List` 。func prepending(head: Element) -> List

/// 如果不空,这返回`self`的第一个元素,/// 否则返回 `nil`。mutating func popFirst() -> Element?

注意:像popFirst等少数情况,摘要应该被多条语句按照不同的情况分成不同的片段。

  • 关于描述下标访问
/// 访问第 `index` 个元素。subscript(index: Int) -> Element { get set }
  • 关于描述创建构造器
/// 创建一个包含 `n` 个 `x` 的实例。init(count n: Int, repeatedElement x: Element)
  • 对于其他声明,要描述声明的实体到底是什么
/// 支持任意位置高效插入删除元素的列表struct List {

  /// 元素从 `self` 开始,若 self 为空则为 `nil`  var first: Element?  ...
  • 自由延伸一个或多个段落以及零散的条目。但是段落应该使用空行进行分割,并使用完整的句子进行描述。
/// Writes the textual representation of each    ←  摘要/// element of `items` to the standard output.///                                              ← 空行/// The textual representation for each item `x` ← 扩展描述/// is generated by the expression `String(x)`.////// - Parameter separator: text to be printed    ?///   between items.                             ?/// - Parameter terminator: text to be printed   ? 参数区///   at the end.                                ?///                                              ?/// - Note: To print without a trailing          ?///   newline, pass `terminator: ""`             ?///                                              ? 标识与其相关的命令/// - SeeAlso: `CustomDebugStringConvertible`,   ?///   `CustomStringConvertible`, `debugPrint`.   ?public func print(  items: Any..., separator: String = " ", terminator: String = "\n")
  • 使用识别的符号文档标记(markup)元素,在适当的情况下将信息加到摘要之外。
  • 熟知并使用符号命令语法识别零散的条目。像 Xcode 等主流开发工具为零散的条目提供了特殊的处理,其关键词如下:
Attention Author Authors Bug
Complexity Copyright Date Experiment  
Important Invariant Note Parameter  
Parameters Postcondition Precondition Remark  
Requires Returns SeeAlso Since  
Throws Todo Version Warning  

命名

明确用法

  • 在名字中包含所有需要的单词从而避免阅读代码的人陷入困惑

例:考虑一个移除集合中移除指定位置的元素的方法。

?extension List {  public mutating func remove(at position: Index) -> Element}employees.remove(at: x)

如果我们省略方法名中的 at,它可能暗示读者该方法会进行搜索并删除集合中等于 x 的元素,而不是用 x 来指示元素在集合中的位置并删除这个元素。

??employees.remove(x) // 不明确: 我们是在删除 x 吗?
  • 删除不需要的单词。名字中的每个单词都应在使用处表明主要信息。

更多的单词或许可以明确意图并消除歧义,但是那些众所周知的冗余信息都可以删掉。尤其是那些仅仅重复类型信息的单词。

??public mutating func removeElement(member: Element) -> Element?allViews.removeElement(cancelButton)

对于这种情况,Element 在调用处没有提供任何要点信息,如下 API 会更好。

?public mutating func remove(member: Element) -> Element?allViews.remove(cancelButton) // 更明了

个别情况下,重复类型信息对于消除歧义是必要的,但一般来说,用一个词来表明参数作用而不是类型的词会更好一些,详见下一条。

  • 变量、参数、关联类型依据作用对其进行命名,而不是基于它们的类型。
??var string = "Hello"protocol ViewController {  associatedtype ViewType : View}class ProductionLine {  func restock(from widgetFactory: WidgetFactory)}

重申一遍类型名其实并不能提升明确性和表现力。相反,你应该尽量选用那些表明实体作用的名字。

?var greeting = "Hello"protocol ViewController {  associatedtype ContentView : View}class ProductionLine {  func restock(from supplier: WidgetFactory)}

如果某个关联类型和它的协议联系非常紧密,导致它的协议名就是它自身的名字,那就给关联类型的名字加上Type避免冲突:

protocol Sequence {  associatedtype IteratorType : Iterator}
  • 为弱类型信息的参数补充信息以表明参数的作用。

当参数类型是NSObjectAny、 AnyObject或者像IntString这样的基本类型的时候,调用处的类型信息和上下文环境可能无法完全表明函数意图。在下面的例子中,它的声明可能明确,但在调用的地方意图不明。

??func add(observer: NSObject, for keyPath: String)grid.add(self, for: graphics) // vague

为了恢复明确性,在每个弱类型参数前加一个名词用来描述它的角色。

?func addObserver(_ observer: NSObject, forKeyPath path: String)grid.addObserver(self, forKeyPath: graphics) // clear

为使用舒适而努力

  • 尽量使方法或函数名在调用的时,符合英语语法规范
?x.insert(y, at: z)          “x, insert y at z”x.subViews(havingColor: y)  “x‘s subviews having color y”x.capitalizingNouns()       “x, capitalizing nouns”
??x.insert(y, position: z)x.subViews(color: y)x.nounCapitalize()

如果不影响方法要表达的含义,那可以简化第一个或者前两个参数,这样使用起来更加流畅。

AudioUnit.instantiate(  with: description,  options: [.inProcess], completionHandler: stopProgressBar)
  • 将工厂方法的命名以 make 开头,如:x.makeIterator()
  • 构造器和工厂方法调用时应由一个不包含第一实参的短语构成,如:x.makeWidget(cogCount: 47)。

举个例子,如下的调用短语都不包含第一实参。

?let foreground = Color(red: 32, green: 64, blue: 128)let newPart = factory.makeWidget(gears: 42, spindles: 14)

而下面这段代码,API 作者试图用第一实参创建符语法连续性的API:

??let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128)let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14)

事实上,这条规则以及下面的参数命名规则都做了这样的处理,这意味着第一实参都会包含一个标签,除非该方法只用来作无损类型转换。

let rgbForeground = RGBColor(cmykForeground)
  • 根据函数和方法的副作用进行命名

    • 没有副作用的方法读起来应该是一个名词词组,如:x.distance(to: y)i.successor()
    • 有副作用的方法读起来应该是一个命令式的动词短语,如:print(x)x.sort()x.append(y)
    • 始终给一对可变方法和不可变方法命名。一个可变方法通常会有一个与之对应的不可变方法,但是不可变方法会返回一个新的值而不是更新现有的实例。
      • 当操作被一个名词描述时,给可变方法使用eding前缀来给动词添加祈使语气。
Mutating Nonmutating 
x.sort() z = x.sorted()
x.append(y) z = x.appending(y)
- 尽量使用动词过去分词来命名不可变方法(通常是添加 `ed`):

/// Reverses `self` in-place.mutating func reverse()/// Returns a reversed copy of `self`.func reversed() -> Self...x.reverse()let y = x.reversed()
- 当添加`ed`时由于动词有一个直接对象而导致不符合语法时,命名不可变方法通常会使用动词的现在分词,即添加`ing`: 
/// Strips all the newlines from \`self\`mutating func stripNewlines()/// Returns a copy of \`self\` with all the newlines stripped.func strippingNewlines() -> String...s.stripNewlines()let oneLine = t.strippingNewlines()
  • 当操作被一个名词描述时,可给不可变方法使用名词并添加form前缀。
Nonmutating     | Mutating

——————–|——————
x = y.union(z) | y.formUnion(z)
j = c.successor(i) | c.formSuccessor(&i)

  • 当使用不可变方法时,布尔型方法和属性读起来应该像是断言,如:x.isEmptyline1.intersects(line2)
  • 用来描述是什么的协议读起来应该是个名词。(如:Collection)。
  • 用来描述能做什么的协议应该加上 able、ible  ing (如: EquatableProgressReporting)。
  • 其它类型、属性、变量和约束的命名都应该用名词

合理使用术语

Term of Art(术语)名词 : 一个词或短语,在特定的领域或行业中有更精确专业的意义。

  • 如果一个常用的词可以清楚地表达意图,就不要使用晦涩难懂的术语。用 “skin” 就可以达到你的目的的话,就别用 “epidermis”了。术语是一种非常必要的交流工具,但是应该用在其他常用语无法表达关键信息的时候。
  • 如果你确实要使用术语,确保它已被明确定义

使用术语的唯一理由是,用其他常用词会表意不清或造成歧义。因此,API 应该严格依据某个术语被广泛接受的释义来进行命名。

  • 不要让专家惊讶:如果我们重新定义了某个术语,那会使熟知它的人感到惊讶甚至是愤怒。
  • 不要让初学者迷惑:初学某个术语的人通常都会上网搜索它的传统释义。
  • 避免缩写。缩写,特别是不标准的缩写,已经算是一种术语了,因为要理解它的意思必须正确地把它翻译成完整版本才行。

所有缩写必须能轻易在互联网中搜索到它的意思。

  • 有例可循。不要为一个新人去优化术语而不遵守现有的规范。

将一个线性的数据结构命名为 Array 比一些更简单的词要好,如 List。尽管 List 对新手来说更易于理解。因为Array(数组) 在现代计算机体系中是个非常基础的概念,每个程序员都已经知道或者能够很快地学会它。总之,请使用那些为程序员所熟知的术语,这样当人们搜索和询问时就能得到回应。

在一些特定的编程领域,譬如数学运算方面,广为人知的sin(x)就比解释性的短语verticalPositionOnUnitCircleAtOriginOfEndOfRadiusWithAngle(x)要好得多。注意,在这种情况下,“有例可循”的优先级大于指南中的“不要使用缩写”,哪怕完整的词是sine。毕竟sin(x)已经被程序员们用了几十年了,更被数学家们用了好几个世纪了。

约定

一般约定

  • 标注那些复杂度不是 O(1) 的计算型属性。人们总是假定存取属性是不需要什么计算的,因为他们把属性保存作为了心智模型。
  • 尽量使用方法和属性,而不是普通函数。普通函数仅适用于一些特定情况:
  1. 当没有明显的 self
min(x, y, z)
  1. 当函数为无约束泛型:
print(x)
  1. 当函数的写法是公认记法的一部分:
sin(x)
  • 遵循常规用例。类型和协议用首字母大写驼峰命名法(UpperCamelCase)进行命名,其它则均为首字母小写(lowerCamelCase)驼峰命名法。

缩写通常在美式英语中会根据首字母情况统一成全大写或者全小写:

var utf8Bytes: [UTF8.CodeUnit];var isRepresentableAsASCII = true;var userSMTPServer: SecureSMTPServer;

其它首字母缩略词当作普通单词处理即可:

var radarDetector: RadarScanner;var enjoysScubaDiving = true;
  • 当几个方法的基本意义相同,或者它们作用在明确的范围内时,可以共享同一个基本命名

例如,我们鼓励下面这种做法,因为这几个方法基本做了相同的事情:

?extension Shape {  /// Returns `true` iff `other` is within the area of `self`.  func contains(other: Point) -> Bool { ... }

  /// Returns `true` iff `other` is entirely within the area of `self`.  func contains(other: Shape) -> Bool { ... }

  /// Returns `true` iff `other` is within the area of `self`.  func contains(other: LineSegment) -> Bool { ... }}

由于泛型和容器都有各自独立的范围,所以在同一个程序里这样使用也是可以的:

?extension Collection where Element : Equatable {  /// Returns `true` iff `self` contains an element equal to  /// `sought`.  func contains(sought: Element) -> Bool { ... }}

然而,下面这些 index 方法有不同的语义,应该采用不同的命名:

??extension Database {  /// Rebuilds the database‘s search index  func index() { ... }

  /// Returns the `n`th row in the given table.  func index(n: Int, inTable: TableID) -> TableRow { ... }}

最后,你还应避免返回值重载,因为在类型推断时会产生歧义:

??extension Box {  /// 如果有值,返回 `Int` 并保存在 `self`  /// 否则返回 `nil`  func value() -> Int? { ... }

  /// 如果有值,返回 `String` 并保存在 `self`  /// 否则返回 `nil`  func value() -> String? { ... }}

形参

func move(from start: Point, to end: Point)
  • 选择能服务于文档编写的参数名。虽然参数名不在方法入口处显示,但它们起到了重要的解释说明。

选择能使文档通俗易懂的参数名。比如,下面这些参数名就是文档读上去很自然:

?/// Return an `Array` containing the elements of `self`/// that satisfy `predicate`.func filter(_ predicate: (Element) -> Bool) -> [Generator.Element]

/// Replace the given `subRange` of elements with `newElements`.mutating func replaceRange(_ subRange: Range, with newElements: [E])

而这些就使文档难以理解且不合语法:

??/// Return an `Array` containing the elements of `self`/// that satisfy `includedInResult`.func filter(_ includedInResult: (Element) -> Bool) -> [Generator.Element]

/// Replace the range of elements indicated by `r` with/// the contents of `with`.mutating func replaceRange(_ r: Range, with: [E])
  • 简化常见使用时利用默认参数的优点。任何有一个常用值的参数都可以使用默认参数。

通过隐藏无关信息,默认参数提高了代码可读性,例如:

??let order = lastName.compare(royalFamilyName, options: [], range: nil, locale: nil)

可以更加简洁:

?let order = lastName.compare(royalFamilyName)

提供默认参数比使用一类方法更可取,因为它减轻了 API 使用者的认知负担。

?extension String {  /// ...描述...  public func compare(     other: String, options: CompareOptions = [],     range: Range? = nil, locale: Locale? = nil  ) -> Ordering}

上面的代码可能不够简洁,但它比如下的代码简洁多了:

??extension String {  /// ...描述 1...  public func compare(other: String) -> Ordering  /// ... 描述 2...  public func compare(other: String, options: CompareOptions) -> Ordering  /// ... 描述 3...  public func compare(     other: String, options: CompareOptions, range: Range) -> Ordering  /// ... 描述 4...  public func compare(     other: String, options: StringCompareOptions,     range: Range, locale: Locale) -> Ordering}

一类方法中的每个方法都需要被分别注释和被使用者理解。决定使用哪个方法之前,使用者必须理解所有方法,并且偶尔会对它们之间的关系感到惊讶——比如,foo(bar: nil) 和 foo() 并不总表示相同的方法——从几乎相同的文档和注释中去区分这些微笑差异是很无聊的。而一个有默认参数的方法将提升编码的体验。

  • 尽量把默认参数放在参数列表末尾。没有默认值的参数通常对于方法的语义来说是必要的,在方法被调用的时候提供了稳定的初始形态。

实参标签

func move(from start: Point, to end: Point)x.move(from: x, to: y)
  • 当不同参数不能被有效区分时,删除所有参数标签,如:min(number1, number2)zip(sequence1, sequence2)
  • 当构造器完全只用作类型转换的时候,删除第一个参数标签,如: Int64(someUInt32)

第一个参数总应该是要被转换的东西。

extension String {  // 将 `x` 转换为给定进制下的文本表示  init(_ x: BigInt, radix: Int = 10)   ← 注意起始的下划线}

text = "值为: "text += String(veryLargeNumber)text += "十六进制为: "text += String(veryLargeNumber, radix: 16)

然而,在缩小转换(Narrowing Conversion)中,用标签来表明范围变窄是推荐的做法。

extension UInt32 {  /// 创建一个精确类型的实例 `value`  init(_ value: Int16)            ← 精度更宽,因此没有标签  /// 创建最低有 32 位的实例 `source`。  init(truncating source: UInt64)  /// 创建最接近描述  `valueToApproximate` 近似值的实例  init(saturating valueToApproximate: UInt64)}
  • 当第一个参数是介词短语的一部分时,应设置参数标签。这时标签一般以介词开头,如 x.removeBoxes(havingLength: 12)

头两个参数各自相当于某个抽象的一部分的情况,是个例外:

??a.move(toX: b, y: c)a.fade(fromRed: b, green: c, blue: d)

在这种情况下,参数的标签应在介词之后,进而保证抽象概念清晰。

?a.moveTo(x: b, y: c)a.fadeFrom(red: b, green: c, blue: d)
  • 此外,如果第一个参数形式是符合语法规范的短语的一部分,删除它的标签,可在方法名后加上短语的前缀词,例如 x.addSubview(y)

这条指南暗示了如果第一个参数不是符合语法的短语的部分形式,那它应该有一个标签。

?view.dismiss(animated: false)let text = words.split(maxSplits: 12)let studentsByName = students.sorted(isOrderedBefore: Student.namePrecedes)

注意,短语必须表达正确的意思非常重要。下面的短语是符合语法正确但单词表达了错误的意思。

??view.dismiss(false)   不 dismiss? Dismiss 一个 Bool 值?words.split(12)       分割数字 12?

注意,默认参数是可以被删除的,在这种情况下它们都不是短语的一部分,所以它们总是应该有标签。

  • 给其它所有参数都加上标签

特殊说明

  • 在 API 中,给闭包参量和元组成员加上标签

这些名字有解释说明的能力,可以出现在文档注释中,并且给元组成员提供了表达渠道。

/// 确保我们至少持有并唯一引用 `requestedCapacity` 元素。////// 若需要更多存储空间, `allocate` 会被调用/// `byteCount` 等于最大对齐的内存单元数////// - Returns:///   - reallocated: 当且仅当分配新内存区为 `true`///   - capacityChanged: 当且仅当 `capacity` 被更新时为 `true`mutating func ensureUniqueStorage(  minimumCapacity requestedCapacity: Int,  allocate: (byteCount: Int) -> UnsafePointer<Void>) -> (reallocated: Bool, capacityChanged: Bool)

尽管在在闭包中使用它们,从技术上来说是实参标签,但即便它们是形参时候,你还是应该在选择这些标签并在文档中使用。
闭包在方法体中被调用时跟调用方法时是一致的,方法签名从一个不包含第一个参数的方法名开始:

allocate(byteCount: newCount * elementSize)
  • 特别注意那些不受约束的类型(如,AnyAnyObject 和一些不受约束的泛型参数),以防在重载时产生歧义。

举个例子,考虑如下重载:

??struct Array {  /// 在 `self.endIndex` 插入 `newElement`  public mutating func append(newElement: Element)

  /// 在 `self.endIndex` 顺序插入 `newElements` 的内容  public mutating func append<    S : SequenceType where S.Generator.Element == Element  >(newElements: S)}

这些方法来自同一组语义,其第一个参数的类型是明确的。但是当 Element 是 Any 时,一个单独的元素和一个元素集合的类型相同。

??var values: [Any] = [1, "a"]values.append([2, 3, 4]) // [1, "a", [2, 3, 4]] 还是 [1, "a", 2, 3, 4]?

为了消除歧义,应将第二个重载方法的命名加以明确。

?struct Array {  /// Inserts `newElement` at `self.endIndex`.  public mutating func append(newElement: Element)

  /// Inserts the contents of `newElements`, in order, at  /// `self.endIndex`.  public mutating func append<    S : SequenceType where S.Generator.Element == Element  >(contentsOf newElements: S)}

注意新的命名是如何更好地匹配文档的注释的。在这种情况下,写文档这种行为其实也在提醒 API 作者自己。

时间: 2024-11-10 08:02:49

Swift 设计指南之 编程规范的相关文章

规范抢先看!微信小程序的官方设计指南和建议

基于微信小程序轻快的特点,我们(微信官方)拟定了小程序界面设计指南和建议. 设计指南建立在充分尊重用户知情权与操作权的基础之上.旨在微信生态体系内,建立友好.高效.一致的用户体验,同时最大程度适应和支持不同需求,实现用户与小程序服务方的共赢. 说到设计规范,这里有一篇绝对不能错过的:<内部教程!超实用6步透视网易设计规范(附完整PDF下载)> 一.友好礼貌 为了避免用户在微信中使用小程序服务时,注意力被周围复杂环境干扰,小程序在设计时应该注意减少无关的设计元素对用户目标的干扰,礼貌地向用户展示

汇集了很多swift 学习指南

https://github.com/ipader/SwiftGuide 1,059   Unstar7,294 Fork1,966 ipader/SwiftGuide CodeIssues 0Pull requests 0WikiPulseGraphs 这份指南汇集了Swift语言主流学习资源,并以开发者的视角整理编排.http://dev.swiftguide.cn 376 commits 3 branches 0 releases 12 contributors Swift 100.0%

Swift 学习指南

github地址:https://github.com/ipader/SwiftGuide Swift 语言指南 @SwiftLanguage 更新于 2016-4-18,更新内容详见 Issue 51.往期更新回顾详见<收录周报> 这份指南汇集了 Swift 语言主流学习资源,并以开发者的视角整理编排.对于精选项目及文章,可直接访问<Swift 项目精选>和<Swift 文章精选>. 目录 入门指引 教程文章 开源项目 推荐网站 开发工具 媒体报道 开放平台 Appl

《Swift开发指南》国内第一本Swift图书上市了

既<苹果Swift编程语言开发指南>视频教程地址:智捷在线课堂,推出以来,受到大家高度关注,再接再厉推出: 国内第一本Swift开发图书:配有同步习题.同步视频教程,并全程展现即将上线的iPhone计算器项目: 分层架构设计解决Swift与Objective-C混合搭配问题:我们会及时更新本书版本欢迎大家关注. 新书预售地址: 京东预售:http://item.jd.com/11516346.html当当预售:http://product.dangdang.com/23535049.html互

Swift 语言指南--来自github学习

@SwiftLanguage 更新于 2016-6-6,更新内容详见 Issue 55.往期更新回顾详见<收录周报> 这份指南汇集了 Swift 语言主流学习资源,并以开发者的视角整理编排.对于精选项目及文章,可直接访问<Swift 项目精选>和<Swift 文章精选>.还有开发者们自己维护的<Swift 开发者.项目.最佳实践>.当然也不能错过那些活跃.优秀的<开发者个人资料页>. 目录 入门指引 教程文章 开源项目 推荐网站 开发工具 媒体报

《Swift开发指南》

<Swift开发指南> 基本信息 作者: 关东升    赵志荣 丛书名: 图灵原创 出版社:人民邮电出版社 ISBN:9787115366245 上架时间:2014-8-5 出版日期:2014 年9月 开本:16开 页码:1 版次:1-1 所属分类:计算机 > 软件与程序设计 > 移动开发 > iPhone 更多关于>>> <Swift开发指南> 编辑推荐 国内第一本Swift开发教程 配有同步习题.同步视频教程,并全程展现即将上线的iPhone

华为C语言编程规范

DKBA华为技术有限公司内部技术规范DKBA 2826-2011.5C语言编程规范2011年5月9日发布 2011年5月9日实施华为技术有限公司Huawei Technologies Co., Ltd.版权所有 侵权必究All rights reserved密级:confidentiality levelDKBA 2826-2011.52011-06-02 华为机密,未经许可不得扩散 Huawei Confidential 第2页,共61页Page 2 , Total61修订声明Revision

Swift语言指南(十)--字符串与字符

原文:Swift语言指南(十)--字符串与字符 字符串是一段字符的有序集合,如"hellow,world"或"信天翁".Swift 中的字符串由 String 类型表示,对应着 Character 类型值的集合. Swift 中的 String 类型为你的编程提供了一个高速的,兼容 Unicode规范 的文本处理方式.Swift 创建和处理字符串的语法轻量可读,与 C 语言的字符串语法颇为相似.字符串的拼接非常简单,只需将两个字符串用 + 运算符相加.字符串的值是否

关于《Swift开发指南》背后的那些事

时间轴(倒叙)2014年8月底在图灵出版社的大力支持下,全球第一本全面.系统.科学的,包含本人多年经验的呕心沥血之作<Swift开发指南>(配有同步视频课程和同步练习)全线重磅推出2014年7月5日苹果宣布Swift语言二十天后,<Swift开发指南>第一稿交予图灵出版社2014年6月9日苹果宣布Swift语言三天后,启动<Swift开发指南>撰写2014年6月2日凌晨1点(北京时间:)在苹果开发者大会WWDC 2014上,苹果宣布了全新的iOS及OS X平台开发语言S