作者:walkingway
Swift 2.2 隨著 iOS 9.3 一同閃亮登場,相較于 Swift 2.1, 2.2 版本做出了許多調(diào)整,從其調(diào)整方向上我們也能一窺 Swift 3.0 的影子,以下內(nèi)容主要來自于蘋果 Swift 官方 Blog,接下來就讓我們進入正題,一睹 Swift 2.2 的廬山真面目:
允許更多的關鍵字來做參數(shù)標簽
SE-0001: Allow (most) keywords as argument labels
參數(shù)標簽是 Swift 中非常 cool 的一個特性,我們可以這么寫:
for i in 1.stride(to: 9, by: 2) {
print(i)
}
這個函數(shù)很簡單,由 1 開始,每次加 2,返回一系列的值,最后的結果要小于 9:
1 3 5 7
上面的函數(shù)如果參數(shù)前沒有 to 或 by 標簽,即 stride(9, 2) 那么代碼將失去自解釋性,別人也很難猜到這些參數(shù)的實際用途。
又假設我們要獲取集合中某個值對應的索引,可以聲明如下方法:
indexOf(value, in: collection)
但是注意在 Swift 2.2 之前的版本,上面這種寫法 Xcode 會報錯,因為 in 是一個關鍵字,想要使用這些關鍵字必須加上單引號:
indexOf(value, `in`: collection)
以前我們定義新的 API 的時候,通常也要避免與這些關鍵字撞車,比如用 within 代替 in。在我們導入 Objective-C APIs 的時候通常會碰到這些問題:
event.touchesMatching([.Began, .Moved], `in`: view)
NSXPCInterface(`protocol`: SomeProtocolType.Protocol)
而在 Swift 2.2,我們開放了除 inout, var 和 let 以外所有的關鍵字,現(xiàn)在他們都可以作為參數(shù) label 來使用了(而不用加單引號)關于語法的影響主要注意以下三方面:
- 函數(shù)調(diào)用中的關鍵字可以隨意使用了,不會產(chǎn)生什么歧義,因為方法調(diào)用時 ":" 總是伴隨著參數(shù)標簽出現(xiàn)。
- 函數(shù)/子類化/初始化 聲明:除
inout,var和let這三個關鍵字之外,使用其他關鍵字沒有什么歧義,因為這些關鍵字后面總是跟隨著‘:’或‘_’比如:
func touchesMatching(phase: NSTouchPhase, in view: NSView?) -> Set<NSTouch>
假如你想在函數(shù)聲明中使用 inout, var 和 let 做為參數(shù)名的話,還是要加單引號
func addParameter(name: String, `inout`: Bool)
- 如果在函數(shù)類型中這三個關鍵字(
inout,var,let)出現(xiàn)的話,是不需要加單引號的,這是因為在這種情況下參數(shù)名后總是跟著 ‘:’
(NSTouchPhase, in: NSView?) -> Set<NSTouch>
(String, inout: Bool) -> Void
元組對象可以進行比較操作了
SE-0015: Tuple comparison operators
元組是以逗號分割的值列表:
let developer = ("Numbbbbb", "Shanks")
let designer = ("Cee", "Sai")
以前想要比較兩個元組,我們需要自己重載操作符
func == (t1: (T, T), t2: (T, T)) -> Bool {
return t1.0 == t2.0 && t1.1 == t2.1
}
拋開每次都要寫這一坨無趣的代碼不說,而且只能比較包含兩個元素的元組。不過在 Swift 2.2 中,我們可以直接比較兩個元組了
let developer = ("Numbbbbb", "Shanks")
let designer = ("Cee", "Sai")
if developer == designer {
print("Matching tuples!")
} else {
print("Non-matching tuples!")
}
Swift 2.2 允許不超過 6 個元素的元組之間進行比較,限制元組的元素個數(shù)主要有兩個原因:
- 每一次比較都需要在基本庫中添加額外的代碼
- 元組的元素過多并不是一種好的編程風格,考慮用結構體代替
可以嘗試下面兩個元組比較
let developer = ("Numbbbbb", 23)
let designer = ("Cee", "Sai")
不出意外地報錯了:

我們重點關注下結尾的部分:
note: overloads for '==' exist with these partially matching parameter lists: ......
((A, B), (A, B)), ((A, B, C), (A, B, C)), ((A, B, C, D), (A, B, C, D)),
((A, B, C, D, E), (A, B, C, D, E)), ((A, B, C, D, E, F), (A, B, C, D, E, F))
Swift 內(nèi)部函數(shù)確實逐字比較了元組的元素,直到 (A, B, C, D, E, F),沒有超過 6 個元素。
為 AnySequence.init 增加約束條件
SE-0014: Constraining AnySequence.init
AnySequence 表示一個無類型的序列,他遵循 SequenceType 協(xié)議,而該協(xié)議擁有一個關聯(lián)類型 associatedtype SubSequence ,而有時候我們需要 SubSequence 也要滿足 SequenceType 協(xié)議
假如我們有一個 _SequenceBox
internal class _SequenceBox<S : SequenceType>
: _AnySequenceBox<S.Generator.Element> { ... }
為了確保 SubSequence 滿足 SequenceType,要做如下限定:
internal class _SequenceBox<
S : SequenceType
where
S.SubSequence : SequenceType,
S.SubSequence.Generator.Element == S.Generator.Element,
S.SubSequence.SubSequence == S.SubSequence
> : _AnySequenceBox<S.Generator.Element> { ... }
反過來,他也會影響 AnySequence.init 做一些限定:
修改前的 AnySequence.init:
public struct AnySequence<Element> : SequenceType {
public init<
S: SequenceType
where
S.Generator.Element == Element
>(_ base: S) { ... }
}
修改后的 AnySequence.init:
public struct AnySequence<Element> : SequenceType {
public init<
S: SequenceType
where
S.Generator.Element == Element,
S.SubSequence : SequenceType,
S.SubSequence.Generator.Element == Element,
S.SubSequence.SubSequence == S.SubSequence
>(_ base: S) { ... }
}
事實上,這些約束應該被應用到 SequenceType 協(xié)議自身上(盡管就目前來看是不太可能了),同我們預期的那樣每個 SequenceType 實現(xiàn)都已經(jīng)自我滿足。
在聲明相關類型時用 associatedtype 來替換 typealias
SE-0011: Replace typealias keyword with associatedtype for associated type declarations
在 Swift 2.2 以前的版本中關鍵字 typealias 可以用來聲明兩種類型
- 類型別名(為已存在的類型起一個別名)
- 關聯(lián)類型(作為占位符類型成為協(xié)議的一部分)
以上兩種聲明應該使用不同的關鍵字,為此我們?yōu)殛P聯(lián)類型準備了新的關鍵字 associatedtype,因此在 Swift 2.2 中 typealias 只能用做類型別名的聲明,所以協(xié)議中使用的關聯(lián)類型只能用 associatedtype,如果用了 typealias 就會報錯:
protocol Prot {
associatedtype Container : SequenceType
typealias Element = Container.Generator.Element // error: cannot declare type alias inside protocol, use protocol extension instead
}
應將 typealias 移到 extension 中去
protocol Prot {
associatedtype Container : SequenceType
}
extension Prot {
typealias Element = Container.Generator.Element
}
命名函數(shù)時帶上參數(shù)標簽
SE-0021: Naming Functions with Argument Labels
因為在 Swift 中,函數(shù)是一等公民,所以函數(shù)可以賦值給變量,當做普通值傳遞。為此我們需要一個函數(shù)類型來對該變量做限定。通常我們會使用函數(shù)名作為主要類型部分,但是有許多基本名字相同的函數(shù),僅僅是參數(shù)或參數(shù)標簽不同而已,比如 UIView:
extension UIView {
func insertSubview(view: UIView, at index: Int)
func insertSubview(view: UIView, aboveSubview siblingSubview: UIView)
func insertSubview(view: UIView, belowSubview siblingSubview: UIView)
}
我們調(diào)用時也是通過參數(shù)標簽來區(qū)分不同的方法:
someView.insertSubview(view, at: 3)
someView.insertSubview(view, aboveSubview: otherView)
someView.insertSubview(view, belowSubview: otherView)
但是,當我們創(chuàng)建一個函數(shù)的引用時,就會產(chǎn)生一個歧義,即無法確定調(diào)用的是 UIView 的哪個方法
let fn = someView.insertSubview // ambiguous: could be any of the three methods
我們可以使用類型注解來消除歧義
let fn: (UIView, Int) = someView.insertSubview // ok: uses insertSubview(_:at:)
let fn: (UIView, UIView) = someView.insertSubview // error: still ambiguous!
但是上面的代碼后者因為 (UIView, UIView) 存在兩個方法(aboveSubview 和 belowSubview),所以還是存在歧義,只能用閉包的方式來指名傳遞的方法:
let fn: (UIView, UIView) = { view, otherView in
button.insertSubview(view, aboveSubview: otherView)
}
這樣做法太乏味了,Swift 2.2 現(xiàn)在允許我們將函數(shù)命名為:函數(shù)名 + 參數(shù)標簽的組合來消除歧義:
let fn = someView.insertSubview(_:at:)
let fn1 = someView.insertSubview(_:aboveSubview:)
同樣的語法也可以用做初始化的引用:
let buttonFactory = UIButton.init(type:)
為指定的方法生成一個 Objective-C 選擇器:
let getter = Selector(NSDictionary.insertSubview(_:aboveSubview:)) // produces insertSubview:aboveSubview:.
引用 Objective-C 的選擇器方法
SE-0022: Referencing the Objective-C selector of a method
在 Swift 2,Objective-C selectors 通常會根據(jù)其字面值寫成字符串常量,比如 "insertSubview:aboveSubview:" 這樣比較容易出錯,例如下面的:
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Tap!", style: .Plain, target: self, action: "buttonTaped")
如果你眼神夠好,會發(fā)現(xiàn)我把 buttonTapped 寫成了 buttonTaped,但 Xcode 也不會給我報錯。這一切在 Swift 2.2 終于得到解決,字符串作為 selector 被 deprecated 了,今后該這么寫 #selector(buttonTapped),這樣發(fā)生拼寫錯誤,也能及時得到編譯器的提醒。
即使在純 Swift 環(huán)境中(完全與 Objective-C 完全無關),我們也可以通過 #selector(Swift 方法名) 的方式來實現(xiàn) Swift 的 selector
control.sendAction(#selector(MyApplication.doSomething), to: target, forEvent: event)
extension MyApplication {
@objc(jumpUpAndDown:)
func doSomething(sender: AnyObject?) { … }
}
創(chuàng)建一個 Selector 的引用
let sel = #selector(UIView.insertSubview(_:at:)) // produces the Selector "insertSubview:atIndex:"
編譯期 Swift 的版本檢查
SE-0020: Swift Language Version Build Configuration
在大部分時候,隨著 Swift 版本更新語法也會有較大調(diào)整,但是第三方類庫的維護者們希望他們的庫能夠同時兼容不同版本的 Swift,目前可行的辦法是同時維護多個分支來支持不同版本的語言。
Swift 2.2 提供了新的選項使你將同一個版本的 Swift 代碼集中在同一個文件中,而編譯器會在編譯時選擇具體的 Swift 版本來執(zhí)行
#if swift(>=2.2)
print("Running Swift 2.2 or later")
#else
print("Running Swift 2.1 or earlier")
#endif
類似于現(xiàn)存的 #if os() 構建選項,這個選項決定了編譯器隨后生成的代碼,如果你使用的是 Swift 2.2,那么第二個 print() 將不會被看到。
其它一些特性
蘋果 Swift 官方 Blog 沒有提到的 Swift 2.2 一些新特性
++ 和 -- 將被取消
Swift 2.2 正式將 ++ 和 -- deprecates 掉了,意味著雖然在 Swift 2.2 版本還能工作,但編譯器會給你一個警告。但在 3.0 版本會被完全移除。
你可以使用 += 1 和 -= 1 來替代,至于為什么要將其移除,有這么幾個解釋:
- 寫 ++ 并不比 +=1 能節(jié)省多少時間
- ++ 對學 Swift 沒有任何幫助,+= 至少可讀性更好
- 傳統(tǒng) C styel for 循環(huán)中的 -- 也被 deprecated 了
傳統(tǒng) C 風格的 for 循環(huán)被干掉了
也就是說下面這種寫法在 2.2 的版本被 deprecates 了
for var i = 1; i <= 10; i += 1 {
print("\(i) SwiftGG awesome")
}
以后要這么寫了:
for i in 1...10 {
print("\(i) SwiftGG awesome")
}
如果想要創(chuàng)建一個由大到小的范圍,你按照下面的寫法編譯或許沒問題,但運行時會崩潰
for i in 10...1 {
print("\(i) SwiftGG awesome")
}
應當這么寫:
for i in (1...10).reverse() {
print("\(i) SwiftGG awesome")
}
另一種選擇是使用標準的快速枚舉來遍歷數(shù)組
var array = Array(1...10)
for number in array {
print("\(number) green bottles")
}
數(shù)組和其他一些 slice types 現(xiàn)在有 removeFirst() 方法了
Swift 2.2 終于為我們帶來了 removeFirst() 方法,該方法將從數(shù)組中移除第一個元素,然后返回給我們,可以試驗一下
var array = Array(1...5)
array.removeFirst()
for number in array {
print("the \(number) bird")
}
使用
removeLast()時要注意,如果是空數(shù)組,會崩潰,因此可以用popLast()來替代,該方法會處理空數(shù)組的情形(返回 nil)
元組 splat 語法被廢除了
們可以用下面的方式定義一個函數(shù),在 Swift 2.2 之前可以有兩種方式調(diào)用
func foo(a : Int, b : Int) {}
第一種我們經(jīng)常使用,為函數(shù)的每個參數(shù)都傳遞相對應的值
foo(42, b : 17)
或者我們可以利用一個大部分開發(fā)者都不那么熟悉的特性(tuple splat)來調(diào)用
let x = (1, b: 2)
foo(x)
后者這種語法糖實在沒什么實際意義,在 Swift 2.2 被 deprecated,將在未來的版本移除。
var 參數(shù)被廢除了
var 參數(shù)提供的益處微乎其微,而且容易讓人與 inout 混淆,因此在 Swift 2.2 中被移除了。
舉個例子 sayHello() 函數(shù)使用了 var 參數(shù):
func sayHello(var name: String, repeat repeatCount: Int) {
name = name.uppercaseString
for _ in 0 ..< repeatCount {
print(name)
}
}
sayHello("numbbbbb", repeat: 5)
結果是 NUMBBBBB 將會被打印 5 遍,這是因為參數(shù) name 經(jīng) var 修飾后成為變量,然后執(zhí)行 uppercaseString 方法轉(zhuǎn)換為大寫,如果沒有 var 關鍵字,name 是常量,執(zhí)行 uppercaseString 會失敗。
var 和 inout 之間的差異非常微妙:
- 使用 var,讓你可以在函數(shù)內(nèi)部修改參數(shù)
- 使用 inout,甚至可以讓你的改變延續(xù)到函數(shù)結束后
我們可以在 Swift 2.2 中這么寫:
func sayHello(name: String, repeat repeatCount: Int) {
let upperName = name.uppercaseString
for _ in 0 ..< repeatCount {
print(upperName)
}
}
sayHello("numbbbbb ", repeat: 5)
重命名 debug 標識符:#line, #function, #file
在 Swift 2.1 和之前的版本,使用 __FILE__, __LINE__, __COLUMN__, 和 __FUNCTION__ 標識符,在編譯時會被替換為文件名、行號、列號和函數(shù)名。
而在 Swift 2.2 這些舊的標識符被更新為 #file, #line, #column 和 #function,如果你之前使用過 Swift 2.0 的 #available 來檢查 iOS 版本,正如官方所說 # 意味這編譯器這里要執(zhí)行替換邏輯。
下面在 printGreeting() 函數(shù)中演示了新舊兩種 debug 標識符:
func sayHello(name: String, repeat repeatCount: Int) {
// old - deprecated!
print("This is on line \(__LINE__) of \(__FUNCTION__)")
// new - shiny!
print("This is on line \(#line) of \(#function)")
let upperName = name.uppercaseString
for _ in 0 ..< repeatCount {
print(upperName)
}
}
sayHello("numbbbbb", repeat: 5)