Swift 2.1-3.1版本演进汇总

有一段时间没有写 Swift 了, 前段时间打算在工程中使用 Swift, 结果发现各种东西都变了, 由于点比较分散, 而且官方文档没有给出具体例子, 只是罗列了修改点, 有些点还不太好理解, 所以自己整理了一下变化, 主要是2.1-3.1的修改, 后续的更新应该也会继续往下面添加, 不过排版的形式还在考虑是最新的排最上面还是最下面...

有必要举出例子的都会尽量举出的例子, 其它的个人觉得没什么特别的就会翻译略过, 具体还是要参考官网的 Revision History. 如有疑问或者错漏, 希望留言, 大家一起交流.

Swift 2.2更新

*增加了条件编译块的内容

相当于增加了 C 语言里面的条件编译宏, 这部分内容在后续的版本中还有增强, #if, #elseif, #else, #endif都和之前没有太大的变化, 主要还是条件有比较多的修改, 可以支持 os 类型和版本, arch, 后续还增加了 Swift 的版本.

*在指定成员描述(Explicit Member Expression)小节中, 增加了仅仅通过名字和参数来区分方法/构造器的内容

标题比较难理解, 其实就是在2.2中判定方法重名的机制变了, 直接看例子:

class SomeClass {
    func someMethod(x: Int, y: Int) {}
    func someMethod(x: Int, z: Int) {}
    func overloadedMethod(x: Int, y: Int) {}
    func overloadedMethod(x: Int, y: Bool) {}
}
let instance = SomeClass()
 
let a = instance.someMethod              // 无法确定
let b = instance.someMethod(x:y:)        // 可以确定
 
let d = instance.overloadedMethod        // 无法确定
let d = instance.overloadedMethod(x:y:)  // 依然无法确定
let d: (Int, Bool) -> Void  = instance.overloadedMethod(x:y:)  // 可以确定

可以看到, 参数名也需要一致了, 如果参数名一致, 但是类型不一致则需要显式写上方法的类型.

*增加了#selector的形式获取 selector

之前的版本都是用字符串来寻找 selector, 没有提示, 很容易出错, 而且不报错, 比较坑爹, 2.2增加了用#selector寻找 selector

*在关联类型和协议关联类型小节中更新了用 associatedtype 来关联类型的内容
// 2.2 以前
protocol Container {
    typealias ItemType  // <-- 之前为 typealias
    mutating func append(item: ItemType)
    var count: Int { get }
    subscript(i: Int) -> ItemType { get }
}

// 2.2
protocol Container {
    associatedtype Item // <-- 修改为 associatedtype
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(i: Int) -> Item { get }
}
*更新了构造失败小节中构造器在实例完全构造好之前返回 nil 的内容

仔细看了一下文档中给出的例子, 没有发现明显的区别, 所以具体也不知道更新了哪里...

*在比较操作符小节中增加了元组的对比

元组的对比需要两边里面的类型是一致, 否则会报错. 直接看官方的例子吧

(1, "zebra") < (2, "apple")   // true, 因为1<2, 后续的就不会比了
(3, "apple") < (3, "bird")    // true, 因为3==3, apple<bird
(4, "dog") == (4, "dog")      // true

(1,2) < ("aa", "bb") // error

*在关键字和标点小节中,增加了使用关键字作为外部参数名的内容

也就是说现在关键字作为参数的标签也是可以的了. 如:

func keywordFunc(func x: Int, while y: String){}
*在声明属性小节中更新了关于 @objc 属性的内容, 现在枚举和它的 case 都可以使用这个属性了

这里增加了一大段描述, 大概就是现在可以给枚举加 @objc 属性了, 然后如果要在 Objc 代码里面使用的话, 类型会自动拼接为 {TYPE}{CASE}, 如:

// xxx.swift
@objc public enum SomeType: Int {
    case Custom
    case System
    case TypeOne
    case None
}

// xxx.m
SomeType type = SomeTypeCustom;

对 case 使用 @objc 没有做多余的阐述, 应该和 class 的属性用是一致的效果.

*更新了操作符小节中, 关于自定义操作符中包含点的内容

现在可以定义一个以点(.)开头的操作符, 例如: 可以定义.+.操作符, 它会被当做一个独立的操作符. 如果一个自定义操作符不是以.开头的, 那么它也不能在后续中包含., 例如+.+会被当做是+后面跟着一个.+操作符

PS: 个人不太建议定义这种奇奇怪怪的操作符, 如果不能一眼看出其作用或者符合一般的认知习惯, 就会变成定义了一个函数叫 a 一样无法理解.

*还是在重新抛出函数和方法小节中, 增加了重新抛出函数不能直接抛出错误

也就是说标记为rethrows的函数不能直接 throw 一个错误, 必须要在 do-catch 中抛出, 如:

func someFunction(callback: () throws -> Void) rethrows {
    do {
    
    } catch {
        throw AnotherError.error
    }
}
*在属性监听小节中, 增加了传入 in-out 参数的时候, 触发属性监听的内容

我觉得这算是之前的一个小bug, 老版本在传入属性作为 in-out 参数的时候并不会调用 willSet 和 didSet, 3.0解决了这个问题:

class Test {
    var num = 1 {
        didSet{
            print("will set")
        }
        willSet{
            print("did set")
        }
    }
}

func testInout( param x : inout Int ){
    x = 2
}

var test = Test()
testInout(param: &test.num)

*在 A Swift Tour中增加了错误处理

在Swift 简介中增加了错误处理的小节

*更新了弱引用的图标, 使得析构过程更加清晰
*移除了 C 风格的 for 循环, 前++, 后++和前--, 后--都被移除了
*移除了可变函数参数和柯里化函数的特殊语法

Swift 3.0 变动

// 2017.1.7增加

* GCD 的修改

GCD 在 Swift 3中得到了彻底的修改, 新语法更适合 Swift 的写法. 例如:

DispatchQueue.global().async {
    // do something in global queue
}
 
DispatchQueue.main.async {
    // do something in main queue   
}

具体 GCD 使用细节可以看看WWDC 视频或者官方文档

*更新了函数章节的内容, 函数声明小节中所有的参数都会默认获得一个参数标签了.

以下列代码为例:

// 函数声明未变
func someFunction(firstParameterName: Int, secondParameterName: Int) {

}

// 3.0前版本调用
someFunction(1, secondParameterName: 2)

// 3.0版本调用
someFunction(firstParameterName: 1, secondParameterName: 2)
*更新了高级操作符章节内容, 现在可以把自定义的操作符作为类型方法来定义了, 而不是之前的全局函数.

高级操作符之前没有在系列文章中体现, 所以暂时不好对比了, 如果可能的话我把以前的文档找出来对比一下. 总之, 看起来现在友好很多, 以前看 swift 的源码的时候会发现一大堆的操作符定义...

*在访问控制章节中增加了对 open 和 fileprivate 的解释.

也就是说访问控制又增加了2个级别, 之前 private 对应了现在的 fileprivate, 列一个列表来说明变化吧:

修饰符 3.0以前版本 3.0版本
open 与 public 一样, 可以被外部引用
public 跨模块可见 跨模块可见
internal 模块内部的任何源文件可见 模块内部的任何源文件可见
private 同一个源文件内可见 当前类型可见
fileprivate 同一个源文件内可见

在3.0中 open 相比较于 public 的区别如下:

 1. open 仅可用于类以及类的成员变量
 2. 用 public 或者更严格的访问级别定义的类, 只能在当前模块(定义该类的模块)被继承
 3. 用 public 或者更严格的访问级别定义的类成员变量, 只能在当前模块被重载
 4. 用 open 定义的类既可以在当前模块继承, 也可以在任意引入该模块的地方继承
 5. 用 open 定义的类成员变量既可以在当前模块重载, 也可以在任意引入该模块的地方重载

上述的几点变化都设计到继承, 重载, 所以 open 仅可用于类和类的成员变量.

*函数中 inout 修饰符的位置发生了变化
// 3.0前
func swapTwoInts(inout a: Int, inout _ b: Int) {
    let t = a
    a = b
    b = t
}
var a = 1, b = 2
swapTwoInts(&a, &b)  // &去掉会报错

// 3.0
var x = 10
func f(a: inout Int, b: inout Int) {
    a += 1
    b += 10
}
f(a: &x, b: &x) // Invalid, in-out arguments alias each other
*更新了逃逸闭包和自动闭包章节的 @noescape@autoclosure 的信息, 目前它们是类型信息, 而不是声明属性了.

@noescape被去除, 增加了正向表意的 @escaping, 位置也有所变化.

// 3.0以前
var completionHandlers: [() -> Void] = [] // 存储闭包用
func someFunctionWithNoescapeClosure(@noescape closure: () -> Void) {
 closure()
 completionHandlers.append(closure) // error
}
func someFunctionWithEscapingClosure(completionHandler: () -> Void) {
 completionHandlers.append(completionHandler)
}

// 3.0
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

@autoclosure没有更名, 只是和 @escaping 一样变了位置

// 3.0以前
func serveCustomer(@autoclosure customerProvider: () -> String) {
 print("Now serving \(customerProvider())!")
}
serveCustomer(customersInLine.removeAtIndex(0))

// 3.0
func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
*高级操作符章节增加了自定义 infix 操作符优先级组的信息

直接看文档吧, 目前不打算深入了解, 看文档

*一系列更新, 包括用 macOS 取代 OS X, Error 替代 ErrorProtocol, 诸如ExpressibleByStringLiteral的协议改名为StringLiteralConvertible.
*更新了泛型章节的 where语句中的泛型小节, 现在 where 语句被写在了声明的最后边

直接看代码对比来看where语句的变化吧

// 3.0以前
func allItemsMatch<
    C1: Container, C2: Container  // <-- 类型约束, 必须实现Container
    where C1.ItemType == C2.ItemType, C1.ItemType: Equatable> // <-- 协议内部关联类型必须相等, 关联类型必须实现Equatable协议
    (someContainer: C1, _ anotherContainer: C2) -> Bool {

    // do something
}

// 3.0
func allItemsMatch<
    C1: Container, C2: Container>
    (_ someContainer: C1, _ anotherContainer: C2) -> Bool
    // where 语句的位置发成了变化, 被放在了尖括号外部
    where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
        
    // do something
}
*更新了逃逸闭包的内容, 现在它默认是非逃逸闭包了

参考上面的变化

*更新了基础章节中的 optional 绑定以及语句章节中 while 语句的内容, 现在 if, while 和 guard 语句用一个逗号分割的列表来替代了 where 语句, 例如:
// 3.0以前
if let firstNumber = Int("4") {
    if let secondNumber = Int("42") {
        if firstNumber < secondNumber && secondNumber < 100 {
            print("\(firstNumber) < \(secondNumber) < 100")
        }
    }
}
// Prints "4 < 42 < 100"

// 3.0
if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
    print("\(firstNumber) < \(secondNumber) < 100")
}
// Prints "4 < 42 < 100"

*在流程控制中的 switch 中的 case 中添加了多种模式匹配.

其实就是说一个case分支现在可以写多个模式匹配了, 如果用原来的写法的话, 就应该要用 fallthrough来写2个 case.

switch control expression {
case pattern 1:
    statements
case pattern 2 where condition:
    statements
case pattern 3 where condition,   <-- 这里
     pattern 4 where condition:
    statements
default:
    statements
}
*更新了关于函数类型的内容, 现在函数参数标签不再属于函数类型的一部分了

直接看看官方的例子:

func someFunction(left: Int, right: Int) {}
func anotherFunction(left: Int, right: Int) {}
func functionWithDifferentLabels(top: Int, bottom: Int) {}
 
var f = someFunction // 现在 f 的类型是 (Int, Int) -> Void, 而不是 (left: Int, right: Int) -> Void 了.
f = anotherFunction              // OK
f = functionWithDifferentLabels  // OK
 
func functionWithDifferentArgumentTypes(left: Int, right: String) {}
func functionWithDifferentNumberOfArguments(left: Int, right: Int, top: Int) {}
 
f = functionWithDifferentArgumentTypes     // Error
f = functionWithDifferentNumberOfArguments // Error
*在协议章节更新了协议组合的内容, 以及类型章节中协议组合使用了 Protocol1 & Protocol2的新用法
// 3.0以前
protocol Named {
    var name: String { get }
}
protocol Aged {
    var age: Int { get }
}
struct Person: Named, Aged {
    var name: String
    var age: Int
}
func wishHappyBirthday(celebrator: protocol<Named, Aged>) { // <-- 区别
    print("Happy birthday \(celebrator.name) - you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(birthdayPerson)

// 3.0

protocol Named {
    var name: String { get }
}
protocol Aged {
    var age: Int { get }
}
struct Person: Named, Aged {
    var name: String
    var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged) { // <-- 区别
    print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
*在动态类型描述的内容, 现在使用新的 type(of:)语法来获取动态类型描述

typeof 的用法官方给了例子, 其实也就是相当于给了个全局的函数, 返回一个 Class 对象.

class SomeBaseClass {
    class func printClassName() {
        print("SomeBaseClass")
    }
}
class SomeSubClass: SomeBaseClass {
    override class func printClassName() {
        print("SomeSubClass")
    }
}
let someInstance: SomeBaseClass = SomeSubClass()
// someInstance 在编译期的时候有一个静态的类型-- SomeBaseClass, 在 runtime 的时候有一个动态的类型--SomeSubClass
type(of: someInstance).printClassName()
// 打印出"SomeSubClass"
*更新了航控制语句小节中使用#sourceLocation(file:line:)语法的内容

根据描述应该是可以自己制定代码的文件名和行数, 一般作为诊断和 debug 来使用.

*更新了函数永不返回中使用新的 Never 类型

swift 定义了一种 Never 的类型, 它指示了函数或者方法不会返回到调用方

*更新了 In-Out 参数小节中的内容--只有非逃逸闭包可以捕获 in-out 参数
*更新了默认参数值小节中关于默认参数的内容, 现在他们不能在函数调用中重新订阅了
*更新了属性章节中属性参数使用冒号

主要是配合 @available来使用, 如下面的例子, @available后面接 typealias(只作用一个, 有多个 typealias 只会作用于下面第一个) 导致 MyProtocol在后续发布后并不能真正地 alias 为MyRenamedProtocol, 只是为编译器提供了提示的信息, 报错会有 fix-it 出现.

// 第一次版本发布
protocol MyProtocol {
    // protocol definition
}
// 后续发布版本重新命名了MyProtocol
protocol MyRenamedProtocol {
    // protocol definition
}
 
@available(*, unavailable, renamed: "MyRenamedProtocol")
typealias MyProtocol = MyRenamedProtocol

enum SomeEnum: MyProtocol {  // <-- 这里报错, 并出现 fix-it, 要求变为MyRenamedProtocol, 
//如果MyRenamedProtocol拼写错误如写成MyRenamedProtocolTest, fix-it 的结果也会编程MyRenamedProtocolTest
    
}
*在重新抛出函数和方法小节中增加了关于在 rethrowing 函数的catch block内部错误的内容.

直白的说, 增加了在标注为 rethrows 的函数/方法中, 只有出现一个异常语句, 基本可以理解为只能出现一个 try.

enum SomeError:Error {
    case error
}

enum AnotherError:Error {
    case error
}


func alwaysThrows() throws {
    throw SomeError.error
}
func someFunction(callback: () throws -> Void) rethrows {
    do {
        try callback()
//        try alwaysThrows()  // 去掉注释就报错
    } catch SomeError.error{
        throw AnotherError.error
    }catch AnotherError.error{
        
    }
}
*增加了#selector 的objc属性的 getter 和 setter 方法描述.

之前的版本都是用字符串来寻找 selector, 没有提示, 很容易出错, 而且不报错, 比较坑爹, 3.0增加了类似原来@selector 的形式, 而且更加好用了.

class SomeClass: NSObject {
    let property: String
    @objc(doSomethingWithInt:)
    func doSomething(_ x: Int) {}
    
    init(property: String) {
        self.property = property
    }
}
let selectorForMethod = #selector(SomeClass.doSomething(_:))
let selectorForPropertyGetter = #selector(getter: SomeClass.property)
*增加了类型别名声明小节中, 关于泛型的别名和在协议中使用别名的内容

类型别名可以使用泛型参数来给定一个已有的泛型起名,例如:

typealias StringDictionary<Value> = Dictionary<String, Value>
 
// 下面两个 dictionary 都是一样的类型
var dictionary1: StringDictionary<Int> = [:]
var dictionary2: Dictionary<String, Int> = [:]

类型别名用泛型参数声明了时候, 参数是可以加限制的, 例如:

typealias DictionaryOfInts<Key: Hashable> = Dictionary<Key, Int>

在协议内部, 类型别名可以提供一个更加简短和方便的名字, 例如:

protocol Sequence {
    associatedtype Iterator: IteratorProtocol
    typealias Element = Iterator.Element
}
 
func sum<T: Sequence>(_ sequence: T) -> Int where T.Element == Int {
    // ...
}
*更新了函数类型小节的内容, 现在参数的圆括号是必须的了

主要是因为元组的原因, ((Int, Int)) -> Void(Int, Int) -> Void还是有很明显的区别的.

*更新了属性章节中, @IBAction, @IBOutlet 和 @NSManaged会附带@objc的内容

也就是说只要加上了上面三种属性的其中之一, 就会自动加上 @objc属性.

*在属性章节中, 增加了 @GKInspectable 的内容

文档的描述如下:

Apply this attribute to expose a custom GameplayKit component property to the SpriteKit editor UI.
应用此属性来暴露一个自定义的 GameplayKit 组件属性到 SpriteKit 编辑器的 UI 上

说实话, 没有玩过 GameplayKit, 只能硬翻了 :p, 字面上的意思也还好理解 GK=gamekit, inspectable, 可检测到的

*更新了可选协议要求小节的内容, 使得这部分代码只用于 objc 变得更加清晰了
// 3.0以前
@objc protocol CounterDataSource {
    optional func incrementForCount(count: Int) -> Int
    optional var fixedIncrement: Int { get }
}

// 3.0
@objc protocol CounterDataSource {
    @objc optional func increment(forCount count: Int) -> Int
    @objc optional var fixedIncrement: Int { get }
}
*移除了函数声明小节中, 在函数参数中显式使用 let 的内容

以前记得是如果整个函数没有对参数进行修改, 就要在参数中加上 let 来修饰, 看起来这个设定被丢弃了.

*移除了语句章节中, Boolean 协议的内容, 现在这个协议已经从 Swift 标准库中移除了
*纠正了声明属性小节中对 @NSApplicationMain的描述

Swift 3.0.1 变动

*更新了 ARC 章节中, weak 和 unowned 引用的内容

weak和 unowned 因为已经找不到2.2的描述, 看了最新的描述, 和之前的理解没有明显的差距, 貌似都是增加了一个 NOTE,
weak 的 NOTE 是:

Property observers aren’t called when ARC sets a weak reference to nil.

weak 属性被自动设置为 nil 的时候不会触发属性观察方法

之前自动设置为 nil 的时候是会触发属性监听的, 新版本改为不触发了.

小 Tip: 如果要验证老的逻辑, 可以在Build Settings里面Use Legacy Swift Language Version 设置为 Yes

unowned 的 NOTE 是:

The examples above show how to use safe unowned references. Swift also provides unsafe unowned references for cases where you need to disable runtime safety checks—for example, for performance reasons. As with all unsafe operations, you take on the responsiblity for checking that code for safety.

You indicate an unsafe unowned reference by writing unowned(unsafe). If you try to access an unsafe unowned reference after the instance that it refers to is deallocated, your program will try to access the memory location where the instance used to be, which is an unsafe operation.

上面的例子展示了如何安全地使用 unowned 引用. Swift 还提供了非安全的 unowned 引用, 来应付不需要在 runtime 时期做安全性检查的场景, 例如为了提高性能. 如其它的非安全操作, 你需要自己为代码安全性负责.

用unowned(unsafe)来标记非安全的 unowned 引用. 如果你尝试访问一个非安全引用的实例, 而且它已经被析构了, 进程会访问这个实例之前存在的内存空间, 这是个不安全的操作.

这个 NOTE 说明 Swift 提供了一种新的, 高性能的引用方式, 只是要付出安全性的代价.

*在声明修饰符章节中增加了unowned, unowned(safe), 和 unowned(unsafe)的说明

上面已经有部分说明了
unowned(safe)unowned的显式写法, 也就是说, unowned 默认就是unowned(safe).
unowned(unsafe)unowned(safe)的区别主要是, unowned(safe)在访问一个已经析构的对象, 会直接触发 runtime error, 而unowned(unsafe) 则会去访问这个实例曾经存在的地址, 其作用则是未知的.

*在Any 和 AnyObject 类型转换小节中增加了关于当值是被期望是 Any 的时候使用 optional 的小记

The Any type represents values of any type, including optional types. Swift gives you a warning if you use an optional value where a value of type Any is expected. If you really do need to use an optional value as an Any value, you can use the as operator to explicitly cast the optional to Any, as shown below.

let optionalNumber: Int? = 3
things.append(optionalNumber)        // Warning
things.append(optionalNumber as Any) // No warning

Any 类型带了了所有的类型, 包括 Optional 类型. 当你在需要 Any 类型的场合使用了 Optional 类型, 编译器会给你一个警告. 如果你确实需要把 Optional 的值当做 Any 类型使用, 可以用as 操作符把它显式转化为Any, 如代码所示.

*在 Expressions 章节中拆分了圆括号表述和元组表述

Swift 3.1 变动

*增加了包含 Where 从句泛型的拓展小节

现在拓展中的泛型也可以加 where 从句了, 来限定拓展的内容:

extension Stack where Element: Equatable {
    func isTop(_ item: Element) -> Bool {
        guard let topItem = items.last else {
            return false
        }
        return topItem == item
    }
}

如果Stack 指定了没有实现Equatable的类型, 在使用 isTop 的时候会报错.

struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue)  // Error
*增加了声明属性小节中关于 Swift 版本的available属性的内容

针对 Swift 版本也可以使用 @available 了, 之前都是平台和系统版本之类的.
@available(swift version-number)

*更新了条件编译块小节中 Swift 版本数字的内容, 现在patch 版本号也允许使用了

可以理解为之前的条件编译也加上了 Swift 版本的控制.

*更新了函数类型小节的内容. 现在 Swift 区分多参数函数和只有一个元组类型参数的函数了

直接上例子吧:

// 3.1以前:
func methodForTuple(t:(a: Int, b: Int, c: Int)){}    
func methodForMulti(a: Int, b : Int, c: Int){}

var tupleFunction = methodForTuple
tupleFunction = methodForMulti // 没有报错, 说明编译器认为这两个个函数是一个类型的

// 3.1之后就会报错了, 因为暂时没有更新 Xcode到最新版, 所以没有验证这一点

推荐阅读更多精彩内容