Swift 4 新功能詳盡介紹:Codable, Dictionaries優化, 多行字符串等等


在幾週前的WWDC大會中,Apple公佈Swift 4,它伴隨新版Xcode 9一同現身,它的最終發布日將會是在九月,這個夏季期間仍是beta版本,這是該語言首次可以向下兼容的版本,它為現有的Swift 3功能提供很棒的優化,其中,很多都是開發者非常期待的,讀者將在本文中了解所有內容,所以廢話不多說,讓我們開始進入重點吧!:)

在本文中,我們假設你已經有Swift 3的一些基本知識。如果讀者需要快速了解的Swift 3和Swift 3.1最大亮點和更新重點,請查看我整理出Swift 3Swift 3.1新特性的介紹文章。

我將使用Playground帶領讀者一起暸解本次程式碼的變更,如果你想充分了解Swift 4的新功能/更改,建議打開Xcode 9 beta版並創建一個新的Playground文件。 本文中的一些示例需要使用Foundation框架才能正常作業,因此,如果你想在Playground中操作本文介紹的所有內容,請先import這個框架之後再開始:

JSON的編碼(Encoding)和解碼(Decoding)

讓我開始介紹一下Swift新版本一個很酷的功能。Swift 4簡化了Swift 3中使用的整個JSON壓縮和序列化過程。現在你只需要使自定義類型實現Codable協定 – 它會將EncodableDecodable兩者結合 – 這樣會讓你的工作更便利:

你的Tutorial類別遵從了Codable協定,所以讓我們繼續編碼你的tutorial物件:

首先,你使用JSONEncoder類別的指定建構器(designated initializer)創建一個code>encoder物件。然後,使用try語句和encoder的encode(_:)方法將tutorial壓縮到data物件中。最後,使用UTF-8編碼將數據轉換為字符串,如果讀者使用Playground跟隨內文一起操作,將看到JSON輸出:

swift-json

現在我們來看看如何拿回初始的tutorial物件:

首先,你使用JSONDecoder類別初始化程序宣告decoder物件。然後,使用像之前那個解碼器的decode(_: from:) 方法,對tryblock內的編碼數據進行解碼。最後且重要的一點是,可以透過字串代換(String interpolation)來使用article物件的屬性,生成自定義字符串,現在解碼JSON數據非常簡單。

swift-json-decode

注意:讀者可以在下列兩個連結閱讀關於此更新的更多訊息,連結一連結二

更智能的Key Paths

Swift 4可以更容易使用key paths訪問物件的屬性,參考下面的類別實作:

你可以使用之前創建的Tutorial類別來定義author物件的某個教程(tutorial),現在來看如何使用key paths獲取作者的名字:

首先,透過(反斜線符號)創建一個Key Path,然後使用keyPath下標(subscript)獲取author物件的名稱。

除此之外 – 你可以使用Key Paths穿越類別階層,決定tutorial物件的標題,如下所示:

開發者甚至可以使用key path的appending(path:)方法向已定義的Key Path添加新的Key Path,如下所示:

最後來看看最精彩的部分 – 使用Key Paths來更改屬性值:

首先,你定義JukeBox類別,並創建一個可以播放新歌曲的jukeBox物件 然後,你可以為song屬性定義Key Path,並使用它來更改歌曲。

注意:你可以在這裡閱讀更多相關資訊。

Classes與Protocols的協作

在創建常量和變量時,你可以在Swift 3中組合使用協定(protocols)。Swift 4則能夠更進一步,讓開發者使用相同的語法將類別添加到其中,你可以將一個物件限制在一個類別和一個協定中,就像在Objective-C中一樣。

想像一下,你正在編寫一個繪圖和著色的應用程序,使用的主要類別和協定原型如下所示:

你可以為形狀(shapes)和線(lines)創建兩個基類(base classes),以及兩個處理繪圖和著色對應邏輯的協定,現在假設你要為你的應用定義一些特定類型:

首先,創建三個實體類別(concrete classes):CircleRectangleSquare,它繼承自Shape基類並遵循DrawableColourable協定。然後你宣告兩個derived classes(衍生類別):StraightLineDottedLine,它們繼承自Line類別,並實現DrawableColourable協定。最後,為之前創建的每個類別定義物件對象。

這種方法的問題是,為應用程序中的每個新類型設置太多的子類,使類別層次結構複雜化,開發者可以透過將兩個協定結合在一起來進行簡化:

你可以使用&操作符,使你的物件遵守DrawableColourable協定,並可以進一步透過為協定創建類型別名(type alias)來簡化語法:

你的物件符合code>DrawableColourable複合協定,但這還不夠,因為初始問題仍然未解決。因此,Swift 4提供一個解方 – 將基礎類別(base classes)添加到組合中,如下所示:

你可以創建ShapeLine的物件,並使用&運算符去實現它們相應的DrawableColourable協定,你的類別層次結構現在很簡單,而且為了簡化語法,你也可以為它創建一個類別別名,如下所示:

你自定義DrawableColourableShapeDrawableLine類型別名,並為其宣告相對應的物件,你的繪圖和著色應用程式設計看起來很棒 – 做得好!:)

進一步閱讀:你可以在這裡閱讀更多相關資訊。

One Sided Ranges

在Swift 4推出了One Sided Ranges(可以簡化範圍運算符為單向),並為half open range和half closed range運算符添加前綴(prefix)和後綴(postfix)版本。以下是Swift 3中如何獲取陣列的前半部分:

你可以使用open range和closed range運算符來獲取你所指定的陣列前半部分,因為是從陣列(array)的第一個元素開始,兩個範圍都以index 0開頭,所以在Swift 4中可以將其省略,如下所示:

你可以刪除範圍的起始index,因為你會從頭開始依序執行陣列中的元素直到對應的halfIndex為止。現在我們來看看如何在Swift 3中返回陣列的後半部分:

這裡沒有什麼新的東西 – 你使用half open range和half closed range操作符,從nextIndex依序執行到陣列的最後一個元素,就像在之前的情況一樣,在Swift 4使用closed ranges不需要列出陣列的最後一個元素的index:

上面程式碼從nextIndex對應的陣列元素計算到陣列的最後一個元素,因此,這邊可以刪除計算範圍的終點index。

One sided ranges運算符讓你從任何定點開始創建出一個index無限延伸的序列,這是在Swift 3中顯示陣列的index和value的方法:

你可以使用for in迴圈和陣列的enumerated()方法為每個index及其相應的value打印一個tuple。陣列的第一個index都為0,因此你應該將其增加1,以1當作計算起點,但是在Swift 4不再需要這樣做了:

你可以在for in迴圈中使用zip(_: _:)函式,將One sided range的index與陣列的value組合在一起,現在第一個index為1。請注意,儘管index序列是無限的,但是當所有陣列的元素都被打印時,這個迴圈就會停止。這是避免在這種情況下創建無限循環的唯一方法:你應該在某種點去終止它,以使其不會像瘋狂一樣繼續下去。

注意:不能在迴圈內省略One sided range的第一個值,因為你將永遠不知道從哪個index開始進行迭代。

One sided range在switch語句中可以做到很好的搭配工作。只有一個注意事項 – 你必須添加一個default情況,使switch不會出現遺漏的情況,因為現在範圍是無限:

你使用單邊無限範圍(infinite ranges)來將其與0進行比較,測試favouriteNumber是正數還是負數。..<00...模式模型相對應(-∞, 0)[0, +∞)區間。請注意,此處不會設置default情況,因此只需使用break語句。

注意:讀者可以在這裡閱讀有關於此變更的更多資訊。

swap vs swapAt

Swift 3中的swap(_:_:)mutating method可讓陣列的兩個元素進行交換:

這個方式有一個最顯著的缺點:交換的元素作為inout參數傳遞給函數,以便可以直接訪問它們。Swift 4採用完全不同的方法,用swapAt(_:_:)替換方法,它接受兩個元素對應的index,然後像之前那樣進行交換:

swap(_:_:)函式也可以在Swift 3中交換兩個給定的值 – 將值作為inout參數傳遞,做法就像剛才那樣:

但是,在Swift 4中swap(_:_:)函式將被棄用並完全刪除,你可以通過兩種方式來替換它,第一種方法是使用另一個常量來執行實際的交換:

第二個解決方案利用Swift內置的tuple特性,只要一行程式碼即可實現以前的算法:

注意:讀者可以在此處閱讀更多相關資訊。

優化Dictionaries和Sets

Dictionaries和Sets是兩個很實用的數據結構,但在Swift 3中一直缺少一些重要特性,所以Swift 4改進了它們,我們先從Dictionaries開始,因為比起Sets的案例,在這裡有更多的變化。

首先,在Swift 4中如何從一個tuples的陣列創建一個dictionary:

使用Dictionaries的init(uniqueKeysWithValues:)初始化程序從tuple陣列創建一個全新的Dictionary。每個tuple表示一周某一天的平均溫度,因此Dictionary的key是星期幾,value是對應的溫度。

注意: 溫度以攝氏度(Celsius degrees)為單位,一週的第一天是星期一,你可以根據自身需求,選擇使用華氏度(Fahrenheit degrees),或是將星期天設為一週的的一天。

如果你已分別擁有用來建構Dictionary中key和value的陣列,可以透過下列方法完成構建作業:

zip(_:_:)函式能從對應的陣列創建出剛才的tuple陣列。但是,如果你只有values的陣列,沒有keys的陣列呢?One sided range又再次提供一個救援方法:

創建一個以1開頭的無限序列,並將其與zip(_:_:)函式中Dictionary的值進行組合:key從1(代表星期一)開始計算到7(代表星期天) – good job!:)

但是如果出現重複的情況呢?Swift 4相當的聰明,它會提供參數讓你自己選擇處理Dictionary重複值的方式,如下所示:

透過Dictionary的init(_: uniquingKeysWith:)初始化方法,創建一個僅有唯一key值的Dictionary。它使用特定的算法為Dictionary中重複的key選擇對應的值。在上述範例中,如果在一周的某一天有兩種不同的溫度,程式會選擇比較小的值。

但是如果要將一些重複項合併到現有的Dictionary怎麼辦?Swift 4也提供了解決方法:

添加元素到Dictionary有兩種風格,可以使用mutating的merge(_: uniquingKeysWith:)方法修改原始Dictionary,或者使用non-mutatingmerging(_: uniquingKeysWith:)函式從頭開始創建一個全新的Dictionary,過濾重複的算法與以前的情況一樣 – 你可以隨意嘗試其他動作,看看會發生什麼事。

在Swift 3當中Dictionary下標(subscripts)返回對應key的value是一個optional,因此你可以使用nil coalescing operator(空值聚合運算子)設置沒有對應key的預設值:

請求的季節(season)不在Dictionary中,因此你設置了預設的溫度。Swift 4替Dictionary中不存在key的對應值添加了全新的subscript,因此在這種情況下不再需要nil coalescing operator:

自定義subscript可以讓你輕鬆更新現有keys的值。若是在沒有它的情況下,你在Swift 3中執行應該會執行下面的動作:

首先,必須使用if let語句拆解季節名稱對應的值,如果不是nil則更新它。現在,我們看看它是如何在Swift 4中使用預設值的subscript來完成:

你將新的subscript與加法賦值運算符相結合,以便在一行代碼中解決任務:)

Swift 4重新設計Dictionary的map(_:)filter(_:),因為上述函式在Swift 3會返回陣列而不是Dictionary。看下面例子,如何在Swift 3中透過map設定Dictionary的value:

在這裡你使用縮略參數(shorthand arguments)將seasons這個Dictionary的溫度從攝氏度映射到華氏溫度。如果你也想映射key值,在Swift 3會這樣做:

這種方法使用Dictionary內的元素創建了的一組tuples陣列,但它不能在Swift 4中運行,因為已經不再能夠對閉包參數進行tuple解構(更多關於文章另一部分),第一個解決方法是使用整個tuple作為map(_:)函數的參數:

第二個解決方案使用縮略語法(shorthand syntax)代替:

這兩個解決方案都可以使用,但是畢竟你仍堅持使用陣列而不是Dictionary,所以讓我們繼續解決這個問題:

你透過mapValues(_:)函式將原本的Dictionary透過map映射一個具有相同結構的全新Dictionary – 多酷?:)

若是使用Swift 3開發,你可以使用缩略語法過濾(filter)Dictionary的值,如下所示:

只有溫度15度以上才能進入filteredArray。但是,如果我們想要過濾之後產生的是一個Dictionary呢?Swift 4再次救援:

程式碼與以前完全相同,但是現在你有一個與原來結構相同的已過濾Dictionary,太好了!:)

注意:你可以使用MARKDOWN_HASH05f152eac01279a44873041576143a65MARKDOWN_HASH函式來決定映射(map)和過濾(filter)的陣列和字典的實際類型。如果想了解相關功能編程技術的更多資訊,請查看我的map教程filter教程

Swift 4讓開發者可以將陣列分解重組為一個按數值分組的Dictionary,如下圖:

Dictionary的init(grouping: by:)初始化方法可以通過特定的key將Dictionary的value進行分類組合。在這種情況下,你可以將scores陣列以數字的位數加以分類,如下所示:

你還可以使用尾隨閉包(Trailing Closures)語法重寫該段程式碼,如下所示:

你可以從初始化函式中將字典裡表達key值的閉包(closure)提取出來,因為在這種情況下,閉包(closure)是初始化方法的最後一個參數 – 真的很酷!:)

Swift 3允許創建一個有預設初始容量的Dictionary,如下所示:

透過Dictionary的init(minimumCapacity:)初始化方法,在ratings這個Dictionary中可儲存至少十個ratings,但無法檢查當前容量或預留更多儲存空間。在此,Swift 4再次幫我們省了不少事:

你可以使用capacity屬性獲取Dictionary的當前儲存空間,並創建內存,以便使用reserveCapacity(_:)方法儲存更多元素。

以上就是關於Dictionary的全部介紹。事實上,Sets和Dictionaries都是屬於集合,所以讓我們看看Dictionary的變更適用於Sets的地方。

Sets是一種特別的陣列,因為它們不能存放相同的項目。儘管如此,當你在Swift 3中過濾一個Set時,將獲得一個陣列,而不是一個Set:

你過濾categories的Set以便只獲取與操作系統(OS)相關的Apple技術,Swift 4會返回一個過濾完成的Set:

程式碼與Swift 3中的代碼相同,但是這次操作系統會儲存在一個Set中,而不是在陣列裡。

你可以在Swift 3中創建一個具有預設容量的Set,如下所示:

這個moviesSet至少儲存十部電影,Swift 4能夠宣告Set的容量並可為存放項目定義額外的空間,將內存容量提升到一個新的水平:

你可以在Set中存更多categories並隨時讀取其容量。這就是Sets!:)

注意: 你可以在此閱讀更多相關變動資訊。

Private vs Fileprivate in Extensions

Swift 3會使用fileprivate訪問控制修飾符,某個類別的重要數據可以在該類別之外的任何地方使用,只要在相同的文件中存取即可,來看看在extension的情況下是如何使用的:

你可以為Person類別創建一個extension,並使用字串代換(string interpolation)來訪問其info()方法中的私有屬性,這是Swift中常見的模式,現在可以在Swift 4中使用private取代fileprivate,以便在extension中訪問類別屬性,就如同你在該類別本身所做:

注意:你可以在此處閱讀更多相關資訊。

NSNumber優化

Swift 3沒有正確地將NSNumber類型轉換為UInt8

在這種情況下,y的正確值應為nil,因為Uint8類型以0開頭,最多只能255。Swift 3採用完全不同的方法來計算餘數:1000%256 = 232。這個奇怪的行為已經在Swift 4中被修復了,所以現在ynil,正如你原先期待的一樣。

注意:讀者可以在此處閱讀有關此更改的更多訊息。

字符的純量(Unicode Scalars)

你不能直接在Swift 3中訪問某個字符的unicode scalars – 必須先將其轉換為字符串,如下所示:

你可以從對應字符串的unicode scalars中決定字符的ASCII代碼,Swift 4替字符添加了一個unicodeScalars屬性,因此可以直接訪問它:

注意:讀者可以在此處獲取更多相關知識。

優化表情符號字串

Swift 3不能正確地判斷表情符串中的字符數:

字符串的字符數應為1,因為在那裡只有一個表情符號,但在這種情況下,Swift 3會返回4。在Swift 4修復了這個問題:

可以看到,你能夠直接訪問字符串本身的count屬性,因為字符串在Swift 4中被視為集合 – 更多內容在文章的另一部分。

多行字符串

在Swift 3你可以為字符的每一行添加\n符號,來宣告一個多行的字符串,並可以在text裡面跳脫(escaping)所有雙引號,如下所示:

Swift 4針對多行字串採用不同的做法,透過三重引號來取代原有方法,所以不必再跳脫(escaping)雙引號:

注意:你可以在此處閱讀更多本次更新資訊。

Generic Subscripts(泛型下標)

Swift 4可以創建一個下標(Subscripts)的泛型:下標的參數和返回類型都可以是泛型,下面則是一個非泛型的Swift 3下標,用於判斷給定陣列中的最大值和最小值:

這邊一步一步的介紹上面的動作:

  1. 你可以在Array類別中創建一個extension,並使用where約束其元素來實現Comparable協定。
  2. 下標採用與陣列的元素相同類型的兩個參數,並返回從其對應參數創建的數組。
  3. 你定義一個陣列,它最後將從subscript這個函式返回 – 開始時是空的。
  4. 你在if let裡拆解原始陣列的最小值,並檢查它是否等於subscript的第一個參數。
  5. 如果通過判斷式,則將最小值添加到剛剛前面建立的陣列中。
  6. 參考步驟4和5尋找原始陣列的最大值和subscript的第二個參數。
  7. 你將陣列返回,完成了

下面是數字和字符串陣列的subscript如何運行:

看起來很酷,但你也可以使其在Swift 4中的Sequences運行,如下所示:

我們來看看與剛才的實作有何差異:

  1. subscript現在是泛型,它帶有兩個參數:一個泛型的Sequence和String類型的type,並且會返回一個泛型陣列:其元素必須符合Equatable協定。
  2. 在這個例子中,同樣定義一個泛型陣列。
  3. 你設定一個替陣列過濾最大值與最小值的泛型條件式,並檢查Sequence是否包含它們。
    <

現在,subscript真的很強大 – 下面來示範如何使用特定的Sequences,如陣列和數字、字符串集合:

注意:你可以在此處閱讀更多相關資訊。

字符串作為集合

Swift 4中字符串是集合,因此現在可以將其視為陣列或序列,並簡化某些任務,例如,下面範例是你如何在Swift 3中過濾給定的字符串:

你在code>for in迴圈來逐個處理字符串的字符,每個字符首先被轉換成一個字符串,然後你檢查該字符串是否實際上不是一個數字。最後,如果通過判斷式,就將對應的字符添加到過濾的字符串中。

Swift 4可以直接在字符串本身使用函數編程技術來直接完成這些操作:

當從一個給定的字符串中定義子串時,Swift 3會返回一個字符串:

你可以使用字符串的substring(to:)方法獲取對應的子字符串,Swift 4則採用了完全不同的方法,它添加一個全新的Substring類型:

在這種情況下,你可以使用one sided ranges來決定子字符串 – 此類型為Substring,而不是StringStringSubstring類型都遵守StringProtocol協定,因此它們的行為完全相同。

注意:你可以在此處獲取本次更新的更多資訊。

優化Objective-C Inference

Swift 3會自動推斷@objc註釋,推薦使用Objective-C中可用的屬性或方法的selectors和key paths。

你創建一個繼承自NSObject的自定義類別,並為其屬性和方法定義selectors和key paths。 Swift [email protected],以便所有內容仍然可以正常工作:

注意:你可以在此處閱讀更多相關資訊。

閉包中的Tuple解構

在Swift 3讓你可以直接使用tuple的組件,只要它們做為一個給定閉包中的參數,以下是實現此功能的方法,使用函數式編程技術映射一組tuple陣列:

你可以使用閉包的參數,將plater的name和team轉為大寫,這在Swift 4中是不再通用,但是有兩個變通方法,第一是使用整個tuple作為閉包的參數,如下所示:

第二個解決方案是使用縮略參數(shorthand arguments):

注意: 這個改變在Swift社群引起了很多爭議,將來很可能會被恢復,所以我們就停留在這裡,你可以在此處閱讀更多相關資訊。

在協定中約束Associated Types

在Swift 4中,你可以約束協定內的Associated types(關聯類型):

首先,你創建一個自定義的協定,並添加符合Sequence協定的關聯類型。然後,使用where子句來限制Sequence的元素:它們都應該實現Equatable協定。

注意: 你可以在此處閱讀更多相關資訊。

結論

這就是所有關於Swift 4的內容,希望你喜歡這個教程,並享受Swift 4的所有變化。同時,如果您有任何問題或疑問,請通知我。 Happy Swifting!:)

譯者簡介:陳奕先-過去為平面財經記者,專跑產業新聞,2015年起跨進軟體開發世界,希望在不同領域中培養新的視野,於新創學校ALPHA Camp畢業後,積極投入iOS程式開發,目前任職於國內電商公司。聯絡方式:[email protected]

FB : https://www.facebook.com/yishen.chen.54
Twitter : https://twitter.com/YeEeEsS

原文What’s New in Swift 4 by Example


Cosmic Pupăză 在部落格 cosminpupaza.wordpress.com 分享有關 Swift 和 iOS 開發文章,以及參與了 raywenderlich.com 和 appcoda.com 兩個平台的 Swift 教學團隊。平日喜歡玩結他和研究二戰歷史。可以透過電郵:[email protected],在Facebook,Twitter 及 Google+ 找到 Cosmic。

blog comments powered by Disqus
訂閲電子報

訂閲電子報

AppCoda致力於發佈優質iOS程式教學,你不必每天上站,輸入你的電子郵件地址訂閱網站的最新教學文章。每當有新文章發佈,我們會使用電子郵件通知你。

已收你的指示。請你檢查你的電郵,我們已寄出一封認證信,點擊信中鏈結才算完成訂閱。

Shares
Share This