Swift3.1新特性初探

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.03.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>中使用的TB<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也是一個全新的類型,并不是AT的類型,為了避免歧義,我們最好還是用一個全新的字母,避免給自己帶來不必要的麻煩。

最后

感謝:
hackingwithswift
泊學網
提供的博客

原創作品,轉載請注明出處:http://www.wzh.wiki

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容