Swift 5.1終於發佈了!本文將帶您瞭解該語言在最新版本中必須提供的改進和更改。git
注意: 當前版本爲Swift 5, iOS 13, Xcode 11, 如轉載本文章, 請聯繫做者, 並給出文章的源地址github
好消息:Swift 5.1如今能夠在Xcode 11 beta版中使用了!這個版本帶來了模塊的穩定性,並改進了具備重要特性的語言。在本教程中,您將瞭解Swift 5.1的新特性。你須要Xcode 11 beta版才能與Swift 5.1兼容,因此在開始以前安裝它吧。編程
Swift 5.1與Swift 5兼容。因爲ABI穩定性,它還與Swift 5以及將來版本的Swift二進制兼容。swift
Swift 5.1在Swift 5中引入的ABI穩定性之上增長了模塊穩定性。雖然ABI穩定性在運行時負責應用程序兼容性,但模塊穩定性使編譯時的庫兼容性成爲可能。 這意味着您能夠將第三方框架與任何編譯器版本一塊兒使用,而不是僅使用它構建的版本。api
每一個教程部分都包含Swift Evolution建議編號,例如**[SE-0001]**。 您能夠經過單擊每一個提案的連接標記來瀏覽每一個更改。數組
我建議您經過在操場上嘗試新功能來學習本教程。 啓動Xcode 11並轉到File ▸ New ▸ Playground。 選擇iOS做爲平臺,選擇空白做爲模板。 將其命名並將其保存在您想要的位置。 開始的時候了!閉包
注:須要重溫Swift 5的亮點嗎?查看Swift 5教程:Swift 5有什麼新功能?app
此版本中有許多語言改進,包括不透明的結果類型,函數構建器,屬性包裝器等。框架
您可使用協議做爲Swift 5中函數的返回類型。less
打開新的Playground後,經過導航到View ▸ Navigators ▸ Show Project Navigator打開項目導航器。 右鍵單擊Sources文件夾,選擇New File並將文件命名爲BlogPost。 使用名爲BlogPost的新協議的定義替換新文件的內容。
public protocol BlogPost {
var title: String { get }
var author: String { get }
}
複製代碼
右鍵單擊頂層Playground並選擇New playground Page。從新命名新的Playground頁面Opaque教程,並粘貼在它:
// 1
struct Tutorial: BlogPost {
let title: String
let author: String
}
// 2
func createBlogPost(title: String, author: String) -> BlogPost {
guard !title.isEmpty && !author.isEmpty else {
fatalError("No title and/or author assigned!")
}
return Tutorial(title: title, author: author)
}
// 3
let swift4Tutorial = createBlogPost(title: "What's new in Swift 4.2?",
author: "Cosmin Pupăză")
let swift5Tutorial = createBlogPost(title: "What's new in Swift 5?",
author: "Cosmin Pupăză")
複製代碼
一步一步來:
createBlogPost(title:author :)
返回Tutorial。createBlogPost(title:author:)
建立swift4Tutorial和swift5Tutorial。您還能夠重用createBlogPost(title:author:)
的原型和邏輯來建立屏幕廣播,由於屏幕廣播也是隱藏在幕後的博客文章。
右鍵單擊頂層Playground並選擇New playground Page。重命名新的Playground頁面Opaque的屏幕截圖,並粘貼到其中:
struct Screencast: BlogPost {
let title: String
let author: String
}
func createBlogPost(title: String, author: String) -> BlogPost {
guard !title.isEmpty && !author.isEmpty else {
fatalError("No title and/or author assigned!")
}
return Screencast(title: title, author: author)
}
let swift4Screencast = createBlogPost(title: "What's new in Swift 4.2?",
author: "Josh Steele")
let swift5Screencast = createBlogPost(title: "What's new in Swift 5?",
author: "Josh Steele")
複製代碼
Screencast實現了BlogPost,所以您能夠從createBlogPost(title:author:)
返回Screencast,並使用createBlogPost(title:author:)
建立swift4Screencast和swift5Screencast。
導航到源文件夾中的BlogPost.swift,並使BlogPost符合Equatable。
public protocol BlogPost: Equatable {
var title: String { get }
var author: String { get }
}
複製代碼
此時,您將獲得一個錯誤,即BlogPost只能用做通用約束。這是由於Equatable有一個名爲Self的關聯類型。具備關聯類型的協議不是類型,即便它們看起來像類型。相反,它們有點像類型佔位符,說「這能夠是任何符合該協議的具體類型」。
Swift 5.1容許您使用這些協議做爲常規類型,使用不透明的結果類型SE-0244。
在Opaque的教程頁面中,向createBlogPost的返回類型添加一些,表示它返回BlogPost的具體實現。
func createBlogPost(title: String, author: String) -> some BlogPost {
複製代碼
相似地,在Opaque的屏幕顯示頁面中,使用some來告訴編譯器createBlogPost返回某種類型的BlogPost。
func createBlogPost(title: String, author: String) -> some BlogPost {
複製代碼
您能夠從createBlogPost: Tutorial或Screencast返回實現BlogPost的任何具體類型。
如今,您能夠檢查以前建立的教程和屏幕截圖是否相同。在Opaque Tutorials的底部,粘貼如下代碼來檢查swift4Tutorial和swift5Tutorial是否相同。
let sameTutorial = swift4Tutorial == swift5Tutorial
複製代碼
在不透明的屏幕截圖的底部,粘貼如下內容,檢查swift4Screencast和swift5Screencast是否相同。
let sameScreencast = swift4Screencast == swift5Screencast
複製代碼
在Swift 5的單表達式函數中使用return:
extension Sequence where Element == Int {
func addEvenNumbers() -> Int {
return reduce(0) { $1.isMultiple(of: 2) ? $0 + $1 : $0 }
}
func addOddNumbers() -> Int {
return reduce(0) { $1.isMultiple(of: 2) ? $0 : $0 + $1 }
}
}
let numbers = [10, 5, 2, 7, 4]
let evenSum = numbers.addEvenNumbers()
let oddSum = numbers.addOddNumbers()
複製代碼
在addEvenNumbers()
和addOddNumbers()
中使用reduce(_:_:)
來肯定偶數和奇數的和。
Swift 5.1下降了單表達式函數的返回值,所以在本例中它們的行爲相似於單行閉包SE-0255:
extension Sequence where Element == Int {
func addEvenNumbers() -> Int {
reduce(0) { $1.isMultiple(of: 2) ? $0 + $1 : $0 }
}
func addOddNumbers() -> Int {
reduce(0) { $1.isMultiple(of: 2) ? $0 : $0 + $1 }
}
}
複製代碼
這一次代碼更簡潔,更容易理解。
注:想了解更多關於
reduce(_:_:)
如何在Swift中工做?查看函數式編程教程: Swift中的函數式編程介紹。
Swift 5.1使用函數構建器實現構建器模式SE-XXXX:
@_functionBuilder
struct SumBuilder {
static func buildBlock(_ numbers: Int...) -> Int {
return numbers.reduce(0, +)
}
}
複製代碼
使用**@_functionBuilder註釋SumBuilder**,使其成爲函數生成器類型。函數構造器是一種特殊類型的函數,其中每一個表達式(文字、變量名、函數調用、if語句等)都是單獨處理的,並用於生成單個值。例如,您能夠編寫一個函數,其中每一個表達式都將該表達式的結果添加到數組中,從而使您本身的數組成爲文字類型。
注意:在Xcode beta中,函數構建器的註釋是**@_functionBuilder**,由於這個建議尚未獲得批准。一旦得到批准,預期註釋將成爲**@functionBuilder**。
經過實現具備特定名稱和類型簽名的不一樣靜態函數,能夠建立函數構建器。buildBlock(_: T...)
是唯一必需的。還有一些函數能夠處理if語句、選項和其餘能夠做爲表達式處理的結構。
使用函數生成器時,要用類名註釋函數或閉包:
func getSum(@SumBuilder builder: () -> Int) -> Int {
builder()
}
let gcd = getSum {
8
12
5
}
複製代碼
傳遞給getSum的閉包計算每一個表達式(在本例中是三個數字),並將這些表達式的結果列表傳遞給構建器。函數構建器以及隱式返回是SwiftUI乾淨語法的構建塊。它們還容許您建立本身的特定於域的語言。
當你在Swift 5中處理計算屬性時,你要處理不少樣板代碼:
var settings = ["swift": true, "latestVersion": true]
struct Settings {
var isSwift: Bool {
get {
return settings["swift"] ?? false
}
set {
settings["swift"] = newValue
}
}
var isLatestVersion: Bool {
get {
return settings["latestVersion"] ?? false
}
set {
settings["latestVersion"] = newValue
}
}
}
var newSettings = Settings()
newSettings.isSwift
newSettings.isLatestVersion
newSettings.isSwift = false
newSettings.isLatestVersion = false
複製代碼
isSwift和isLatestVersion在設置中獲取和設置給定鍵的值。Swift 5.1經過定義屬性包裝器SE-0258去除重複代碼:
// 1
@propertyWrapper
struct SettingsWrapper {
let key: String
let defaultValue: Bool
// 2
var wrappedValue: Bool {
get {
settings[key] ?? defaultValue
}
set {
settings[key] = newValue
}
}
}
// 3
struct Settings {
@SettingsWrapper(key: "swift", defaultValue: false) var isSwift: Bool
@SettingsWrapper(key: "latestVersion", defaultValue: false)
var isLatestVersion: Bool
}
複製代碼
以上代碼的工做原理以下:
默認狀況下,Swift 5不會爲結構中的屬性設置初始值,因此您能夠爲它們定義自定義初始化器:
struct Author {
let name: String
var tutorialCount: Int
init(name: String, tutorialCount: Int = 0) {
self.name = name
self.tutorialCount = tutorialCount
}
}
let author = Author(name: "George")
複製代碼
在這裏,若是做者經過了測試並在網站上加入了教程團隊,則將tutorialCount設置爲0。
Swift 5.1容許直接設置結構屬性的默認值,所以再也不須要自定義初始化器SE-0242:
struct Author {
let name: String
var tutorialCount = 0
}
複製代碼
這一次代碼更乾淨、更簡單。
在Swift 5中,你不能使用Self來引用數據類型的靜態成員,因此你必須使用類型名:
struct Editor {
static func reviewGuidelines() {
print("Review editing guidelines.")
}
func edit() {
Editor.reviewGuidelines()
print("Ready for editing!")
}
}
let editor = Editor()
editor.edit()
複製代碼
網站上的編輯在編輯教程以前會檢查編輯指南,由於它們老是在變化。
你能夠用Swift 5.1 SE-0068中的Self重寫整個代碼:
struct Editor {
static func reviewGuidelines() {
print("Review editing guidelines.")
}
func edit() {
Self.reviewGuidelines()
print("Ready for editing!")
}
}
複製代碼
此次使用Self調用reviewGuidelines()
。
您能夠在Swift 5.1 SE-0245中建立未初始化的數組:
// 1
let randomSwitches = Array<String>(unsafeUninitializedCapacity: 5) {
buffer, count in
// 2
for i in 0..<5 {
buffer[i] = Bool.random() ? "on" : "off"
}
// 3
count = 5
}
複製代碼
逐步瀏覽上述守則:
init(unsafeUninitializedCapacity:initializingWith:)
建立具備特定初始容量的隨機開關。random()
設置每一個開關狀態。Swift 5.1容許您肯定有序集合之間的差別SE-0240。
假設有兩個數組:
let operatingSystems = ["Yosemite",
"El Capitan",
"Sierra",
"High Sierra",
"Mojave",
"Catalina"]
var answers = ["Mojave",
"High Sierra",
"Sierra",
"El Capitan",
"Yosemite",
"Mavericks"]
複製代碼
operatingSystems包含了全部的macOS版本,從最老的版本到最新的版本。答案以相反的順序列出它們,同時添加和刪除其中一些。
區分集合要求您使用#if Swift(>=)
檢查最新的Swift版本,由於全部區分方法都標記爲**@available for Swift 5.1**:
#if swift(>=5.1)
let differences = operatingSystems.difference(from: answers)
let sameAnswers = answers.applying(differences) ?? []
// ["Yosemite", "El Capitan", "Sierra", "High Sierra", "Mojave", "Catalina"]
複製代碼
獲取操做系統和答案之間的difference(from:)
,並使用apply(_:)
將它們應用於答案。
或者,你也能夠手動操做:
// 1
for change in differences.inferringMoves() {
switch change {
// 2
case .insert(let offset, let element, let associatedWith):
answers.insert(element, at: offset)
guard let associatedWith = associatedWith else {
print("\(element) inserted at position \(offset + 1).")
break
}
print("""
\(element) moved from position \(associatedWith + 1) to position
\(offset + 1).
""")
// 3
case .remove(let offset, let element, let associatedWith):
answers.remove(at: offset)
guard let associatedWith = associatedWith else {
print("\(element) removed from position \(offset + 1).")
break
}
print("""
\(element) removed from position \(offset + 1) because it should be
at position \(associatedWith + 1).
""")
}
}
#endif
複製代碼
下面是這段代碼的做用:
inferringMoves()
肯定差別中的移動,並循環遍歷它們。.insert(offset:element:associatedWith:)
,則在偏移量處向答案添加元素;若是associatedWith不是nil,則將插入視爲移動。.remove(offset:element:associatedWith:)
,則從答案的偏移處刪除元素,若是associatedWith不是nil,則認爲刪除是一個移動。Swift 5.1容許您在類中聲明靜態和類下標SE-0254:
// 1
@dynamicMemberLookup
class File {
let name: String
init(name: String) {
self.name = name
}
// 2
static subscript(key: String) -> String {
switch key {
case "path":
return "custom path"
default:
return "default path"
}
}
// 3
class subscript(dynamicMember key: String) -> String {
switch key {
case "path":
return "custom path"
default:
return "default path"
}
}
}
// 4
File["path"]
File["PATH"]
File.path
File.PATH
複製代碼
事情是這樣的:
注:想了解更多關於斯威夫特下標?查看下標教程: 自定義Swift下標。
Swift 5.1實現鍵路徑的動態成員查找SE-0252:
// 1
struct Point {
let x, y: Int
}
// 2
@dynamicMemberLookup
struct Circle<T> {
let center: T
let radius: Int
// 3
subscript<U>(dynamicMember keyPath: KeyPath<T, U>) -> U {
center[keyPath: keyPath]
}
}
// 4
let center = Point(x: 1, y: 2)
let circle = Circle(center: center, radius: 1)
circle.x
circle.y
複製代碼
一步一步來:
注:須要更多關於如何在斯威夫特動態成員查找工做的細節?查看動態特性教程: Swift中的動態特性。
你能夠在Swift 5.1中使用元組的關鍵路徑:
// 1
struct Instrument {
let brand: String
let year: Int
let details: (type: String, pitch: String)
}
// 2
let instrument = Instrument(brand: "Roland",
year: 2019,
details: (type: "acoustic", pitch: "C"))
let type = instrument[keyPath: \Instrument.details.type]
let pitch = instrument[keyPath: \Instrument.details.pitch]
複製代碼
事情是這樣的:
Swift 5.1自動合成具備弱和無標識存儲特性的結構的Equatable和Hashable一致性。
假設您有兩個類:
class Key {
let note: String
init(note: String) {
self.note = note
}
}
extension Key: Hashable {
static func == (lhs: Key, rhs: Key) -> Bool {
lhs.note == rhs.note
}
func hash(into hasher: inout Hasher) {
hasher.combine(note)
}
}
class Chord {
let note: String
init(note: String) {
self.note = note
}
}
extension Chord: Hashable {
static func == (lhs: Chord, rhs: Chord) -> Bool {
lhs.note == rhs.note
}
func hash(into hasher: inout Hasher) {
hasher.combine(note)
}
}
複製代碼
經過實現==(lhs:rhs:)
和hash(into:)
, Key和Chord都符合Equatable和Hashable。
若是你在結構體中使用這些類,Swift 5.1將可以合成Hashable:
struct Tune: Hashable {
unowned let key: Key
weak var chord: Chord?
}
let key = Key(note: "C")
let chord = Chord(note: "C")
let tune = Tune(key: key, chord: chord)
let chordlessTune = Tune(key: key, chord: nil)
let sameTune = tune == chordlessTune
let tuneSet: Set = [tune, chordlessTune]
let tuneDictionary = [tune: [tune.key.note, tune.chord?.note],
chordlessTune: [chordlessTune.key.note,
chordlessTune.chord?.note]]
複製代碼
Tune是Equatable和Hashable,由於value和chord是Equatable和Hashable。
由於它是Hashable,你能夠將tune與chordlessTune進行比較,將它們添加到tuneSet並將它們用做tuneDictionary的鍵。
Swift 5.1爲可選枚舉狀況生成警告:
// 1
enum TutorialStyle {
case cookbook, stepByStep, none
}
// 2
let style: TutorialStyle? = .none
複製代碼
這是如何工做的:
.none
在這種狀況下的含義是什麼:Optional.none
或TutorialStyle.none
。您可使用可選模式將非選項與Swift 5中的可選枚舉進行匹配:
// 1
enum TutorialStatus {
case written, edited, published
}
// 2
let status: TutorialStatus? = .published
switch status {
case .written?:
print("Ready for editing!")
case .edited?:
print("Ready to publish!")
case .published?:
print("Live!")
case .none:
break
}
複製代碼
上面的代碼執行如下操做:
在這種狀況下,Swift 5.1刪除了可選的模式匹配:
switch status {
case .written:
print("Ready for editing!")
case .edited:
print("Ready to publish!")
case .published:
print("Live!")
case .none:
break
}
複製代碼
這段代碼更清晰,更容易理解。
注意:想要了解有關Swift中模式匹配的更多信息? 查看模式匹配教程:Swift中的模式匹配。
Swift 5.1爲字符串添加了一些急需的功能SE-0248:
UTF8.width("S")
UTF8.isASCII(83)
複製代碼
在這裏,您肯定Unicode標量值的UTF-8編碼寬度,並檢查給定的代碼單元是否表示ASCII標量。 查看您可使用的其餘API的提案。
Swift 5.1對連續字符串實現重要更改SE-0247:
var string = "**Swift 5.1**"
if !string.isContiguousUTF8 {
string.makeContiguousUTF8()
}
複製代碼
您檢查UTF-8編碼的字符串是否與isContiguousUTF8連續,並使用makeContiguousUTF8()
來實現,若是不是。 看一下提案,看看你能夠用連續的字符串作些什麼。
您應該瞭解Swift 5.1中的一些其餘功能:
Swift 5.1改進了元組類型的轉換:
let temperatures: (Int, Int) = (25, 30)
let convertedTemperatures: (Int?, Any) = temperatures
複製代碼
您能夠爲convertedTemperatures分配溫度,由於在這種狀況下您能夠將(Int, Int)
轉換爲(Int?, Any)
。
您能夠在Swift 5中聲明帶有重複標籤的元組:
let point = (coordinate: 1, coordinate: 2)
point.coordinate
複製代碼
在這種狀況下,不清楚座標是否從點返回第一個或第二個元素,所以Swift 5.1刪除了元組的重複標籤。
Swift 5更喜歡任何參數而不是泛型參數,只有一個參數的函數重載:
func showInfo(_: Any) -> String {
return "Any value"
}
func showInfo<T>(_: T) -> String {
return "Generic value"
}
showInfo("Swift 5")
複製代碼
在這種狀況下,showInfo()
返回「Any value」。 Swift 5.1以相反的方式工做:
func showInfo(_: Any) -> String {
"Any value"
}
func showInfo<T>(_: T) -> String {
"Generic value"
}
showInfo("**Swift 5.1**")
複製代碼
showInfo()
此次返回「Generic value」。
你不能在Swift 5中爲**@autoclosure**參數聲明類型別名:
struct Closure<T> {
func apply(closure: @autoclosure () -> T) {
closure()
}
}
複製代碼
apply(closure :)
在這種狀況下使用autoclosure聲明閉包。 您能夠在Swift 5.1中的apply(closure :)
原型中使用類型別名:
struct Closure<T> {
typealias ClosureType = () -> T
func apply(closure: @autoclosure ClosureType) {
closure()
}
}
複製代碼
apply(closure :)
此次使用ClosureType進行閉包。
若是你的類包含一個在Swift 5中返回Self的**@objc方法,你必須從NSObject**繼承:
class Clone: NSObject {
@objc func clone() -> Self {
return self
}
}
複製代碼
由於Clone擴展了NSObject,因此clone()
返回Self。 在Swift 5.1中再也不是這種狀況:
class Clone {
@objc func clone() -> Self {
self
}
}
複製代碼
克隆此次沒必要繼承任何東西。
您能夠在Swift 5.1中使用**-enable-library-evolution來更改庫類型而不會破壞其ABI**。 標記爲**@frozen**的結構和枚舉不能添加,刪除或從新排序存儲的屬性和案例SE-0260。
您可使用本教程頂部或底部的「下載材料」連接下載最終的Playground。
Swift 5.1爲Swift 5中已經引入的功能添加了許多不錯的功能。它還爲語言帶來了模塊穩定性,並實現了WWDC中引入的新框架(如SwiftUI和Combine)所使用的複雜範例。
您能夠在官方Swift CHANGELOG或Swift標準庫差別上閱讀有關此Swift版本更改的更多信息。
您還能夠查看Swift Evolution提案,瞭解下一版Swift的內容。 在這裏,您能夠爲當前審覈的提案提供反饋,甚至能夠自行提交提案!
項目示例: 工程示例