Apple最近發布了Xcode8.3, 以及Swift的一個小版本3.1。 不過不要擔心, Swift3.1和Swift3是兼容的,這不會給你的Swift3項目造成太多的麻煩。不幸的是, Xcode8.3無情的去掉了對Swift2.3的支持, 所以, 如果你的項目在使用3.0之前的版本,個人建議還是不要著急更新。
數值類型的failable initialize
這個改動, 來自于SE-0080。Swift為所有的數字類型定義了failable initializer
, 當構造失敗的時候, 就會返回nil
。
Add a new family of numeric conversion initializers with the following signatures to all numeric types:
// Conversions from all integer types.
init?(exactly value: Int8)
init?(exactly value: Int16)
init?(exactly value: Int32)
init?(exactly value: Int64)
init?(exactly value: Int)
init?(exactly value: UInt8)
init?(exactly value: UInt16)
init?(exactly value: UInt32)
init?(exactly value: UInt64)
init?(exactly value: UInt)
// Conversions from all floating-point types.
init?(exactly value: Float)
init?(exactly value: Double)
#if arch(i386) || arch(x86_64)
init?(exactly value: Float80)
#endif
OK, 再讓我們更直觀的感受一下這個方法:
/// 行尾注釋為運行結果
let a = 1.11
let b = Int(a) //!< 1
let c = Int(exactly: a) //!< nil
let d = 1.0
let e = Int(exactly: d) //!< 1
在上面這段代碼中, 我們可以看到1.11 -> Int(exactly:) -> c
的結果為nil
, 而1.0 -> Int() -> e
的結果卻是成功的。其實不難發現, Int(exactly:)
比Int()
的精度檢查更加嚴格, 不會允許精度丟失的情況。因此Int(exactly:)
轉化時,如果丟失精度, 會返回nil。
為什么要加這個特性呢?或者說這個特性的應用場景是什么呢?SE中是這么說的:
It is extremely common to receive loosely typed data from an external source such as json. This data usually has an expected schema with more precise types. When initializing model objects with such data runtime conversion must be performed. It is extremely desirable to be able to do so in a safe and recoverable manner. The best way to accomplish that is to support failable numeric conversions in the standard library.
這段話的大體意思就是,如果你要把一個類似Any這樣的松散類型轉換成數字類型的時候,像服務端返回的json數據,這個特性就會提現他的價值了。
Sequence中新添加的兩個篩選元素的方法
這個特性是根據SE-0045改動的。初步意愿如下:
Modify the declaration of Sequence with two new members:
protocol Sequence {
// ...
/// Returns a subsequence by skipping elements while `predicate` returns
/// `true` and returning the remainder.
func drop(while predicate: (Self.Iterator.Element) throws -> Bool) rethrows -> Self.SubSequence
/// Returns a subsequence containing the initial elements until `predicate`
/// returns `false` and skipping the remainder.
func prefix(while predicate: (Self.Iterator.Element) throws -> Bool) rethrows -> Self.SubSequence
}
Also provide default implementations on Sequence that return AnySequence, and default implementations on Collection that return a slice.
LazySequenceProtocol and LazyCollectionProtocol will also be extended with implementations of drop(while:) and prefix(while:) that return lazy sequence/collection types. Like the lazy filter(_:), drop(while:) will perform the filtering when startIndex is accessed.
所添加的兩個新方法如下:
-
prefix(while:)
:從第一個元素開始,將符合while
條件的元素添加進數組A,如果while
條件不被滿足,則終止判斷,并返回數組A。 -
drop(while:)
:從第一個元素開始,跳過符合while
條件的元素,如果while
條件不被滿足,則終止判斷,并將剩余的元素裝進數組返回。
具體看下面代碼:
let arr = ["ab","ac","aa","ba","bc","bb"]
let a = arr.prefix{$0.hasPrefix("a")}
let b = arr.prefix{$0.hasPrefix("b")}
let c = arr.drop {$0.hasPrefix("a")}
print(a) //!< ["ab", "ac", "aa"]
print(b) //!< []
print(c) //!< ["ba", "bc", "bb"]
可以看到,a
打印出了["ab", "ac", "aa"]
,arr
中第0,1,2個元素都滿足while條件,但是第3個元素開始不滿足條件,所以,a
收到的賦值是第0,1,2三個元素的一個數組。b
打印的是一個[]
,因為arr
中的第一個元素就不滿足while
的條件,所以判斷直接終止,返回一個空數組。
c
打印出了["ba", "bc", "bb"]
,因為arr
中的第0,1,2個元素都滿足while
條件,前綴包含a
,所以都被跳過,到第3個元素的時候,ba
的前綴并不是a
,所以第3個元素之后的所有元素"ba", "bc", "bb"
都被裝進數組返回。
通過available約束Swift版本
之前版本的swift語言,如果你要控制不同版本里面的API,可能需要像下面這樣聲明:
#if swift(>=3.1)
func test() {}
#elseif swift(>=3.0)
func test1() {}
#endif
#if
是通過編譯器處理的,也就是說編譯器要為每一個if
條件獨立編譯一遍。也就是說如果我們的方法要在Swift3.0
和 3.1
可用,那么編譯器就得編譯兩遍。
這當然不是一個好的方法。一個更好的辦法,應該是只編譯一次,然后在生成的程序庫包含每個API可以支持的Swift版本。
為此,在SE-0141中,Swift對@available進行了擴展,現在它不僅可以用于限定操作系統,也可以用來區分Swift版本號了。
首先,為了表示某個API從特定版本之后才可用,可以這樣:
@available(swift 3.1)
func test() {}
其次,為了表示某個API可用的版本區間,可以這樣:
@available(swift, introduced: 3.0, obsoleted: 3.1)
func test() {}
使用具體類型在extension中約束泛型參數
在Swift3.1
之前的版本中,如果想要在Int?
類型添加一個方法的話,可能需要這樣做:
protocol IntValue {
var value: Int { get }
}
extension Int: IntValue {
var value: Int { return self }
}
extension Optional where Wrapped: IntValue {
func lessThanThree() -> Bool {
guard let num = self?.value else { return false }
return num < 3
}
}
聲明了一個協議,給Int寫了一個擴展,就是為了給Int?
添加lessThanThree()
方法,很明顯,這不是一個好的解決方法。
在Swift 3.1
里,我們有更優雅的實現方法:
extension Optional where Wrapped == Int {
func lessThanThree() -> Bool {
guard let num = self else { return false }
return num < 3
}
}
臨時轉換成可逃逸的closure
在Swift3.0
函數的closure
類型參數默認從escaping
變成了non-escaping
。這很好理解,因為大多數用于函數式編程的closure
參數的確都以non-escaping
的方式工作。
但這樣也遇到了一個問題,就是有時候,我們需要把non-escaping
屬性的closure
,傳遞給需要escaping
屬性closure
的函數。來看個例子:
func subValue(in array: [Int], with: () -> Int) {
let subArray = array.lazy.map { $0 - with() }
print(subArray[0])
}
注意,上面代碼是不能編譯通過的。因為lazy.map()
是escaping closure
,而with()
是默認的non-escaping closure
。如果根據編譯器的指引,我們可能會在with:
的后面添加@escaping
:
func subValue(in array: [Int], with:@escaping () -> Int) {
let subArray = array.lazy.map { $0 + with() }
print(subArray[0])
}
這很明顯不是我們想要的設計。幸運的是,Swift3.1
給出了一個臨時轉換的方法:withoutActuallyEscaping()
,我們的方法可以改寫成下面這樣:
func subValue(in array: [Int], with: () -> Int) {
withoutActuallyEscaping(with) { (escapingWith) in
let subArray = array.lazy.map { $0 + escapingWith() }
print(subArray[0])
}
}
withoutActuallyEscaping
有兩個參數,第一個參數表示轉換前的non-escaping closure
,第二參數也是一個closure
,用來執行需要escaping closure
的代碼,它也有一個參數,就是轉換后的closure
。因此,在我們的例子里,escapingWith
就是轉換后的with
。
順帶說一句,這里面使用了array.lazy.map()
而不是array.map()
,是因為array.lazy.map()
會延遲實現的時間,并且按需加載,上面的例子中,只有print(subArray[0])
使用了一次subArray
,所以閉包里的代碼只會執行一次。而用array.map()
則會遍歷整個數組,具體的差異大家自己code試驗吧。
關于內嵌類型的兩種改進
在Swift 3.1
里,內嵌類型有了兩方面變化:
- 普通類型的內嵌類型可以直接使用其外圍類型的泛型參數;
- 泛型類型的內嵌類型可以擁有和其外圍類型完全不同的泛型參數;
在之前的版本中,我們實現一個鏈表中的節點可能需要這樣:
class A<T> {
class B<T> {
var value: T
init(value: T) {
self.value = value;
}
}
}
但這里,就有一個問題了,在A<T>
中使用的T
和B<T>
中的T
是同一個類型么?為了避開這種歧義,在Swift 3.1
里,我們可以把代碼改成這樣:
class A<T> {
class B {
var value: T
init(value: T) {
self.value = value
}
}
}
這就是內嵌類型的第一個特性,盡管B
是一個普通類型,但它可以直接使用A<T>
中的泛型參數,此時B.value
的類型就是A
中元素的類型
接下來,我們再來看一個內嵌類型需要自己獨立泛型參數的情況。:
class A<T> {
class C<U> {
var value: U? = nil
}
}
這就是內嵌類型的第二個改進,內嵌類型可以和其外圍類型有不同的泛型參數。這里我們使用了U
來表示C.value
的類型。其實,即便我們在C
中使用T
作為泛型符號,在C
的定義內部,T
也是一個全新的類型,并不是A
中T
的類型,為了避免歧義,我們最好還是用一個全新的字母,避免給自己帶來不必要的麻煩。
最后
感謝:
hackingwithswift
泊學網
提供的博客
原創作品,轉載請注明出處:http://www.wzh.wiki