在 7 月 29 日的發布的 Xcode 11 beta 5 中,包含了 Swift 5.1。如果想要體驗這些新特性,需要至少安裝好這個版本的 Xcode。本文內容主要參考 Raywenderlich 的這篇文章 編寫,如果想要查看原文,請點擊鏈接查看。
Swift 5.1 在 5.0 引入的 ABI 穩定性基礎上增加了模塊穩定性。雖然 ABI 穩定性在運行時考慮到應用程序的兼容性,但模塊穩定性在編譯時支持庫的兼容性。這意味著你可以在任何編譯器版本中使用第三方框架,而不只是構建它的那個版本。
下面我們一起看一下有哪些改進。
模糊的結果類型 (Opaque Result Types)
在開發的時候,有時候可能會使用 protocol 作為返回值類型。下面來看一個例子:
protocol BlogPost {
var title: String { get }
var author: String { get }
}
struct Tutorial: 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 Tutorial(title: title, author: author)
}
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?")
上面代碼:1)首先定義 BlogPost
協議;2)定義 Tutorial
并實現 BlogPost
協議;3)定義 createBlogPost()
方法用于創建 BlogPost
;4)用 createBlogPost()
創建 swift4Tutorial
和 swift5Tutorial
兩個實例。
下面我們想比較 swift4Tutorial
和 swift5Tutorial
是否相等:
// 錯誤:Binary operator '==' cannot be applied to two 'BlogPost' operands
let isSameTutorial = (swift4Tutorial == swift5Tutorial)
因為 BlogPost
還沒有實現 Equatable
,所以會出錯。下面讓 BlogPost
繼承自 Equatable
:
protocol BlogPost: Equatable {
var title: String { get }
var author: String { get }
}
這時另一個錯誤出現在 createBlogPost()
方法:
// Protocol 'BlogPost' can only be used as a generic constraint because it has Self or associated type requirements
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)
}
這個錯誤的意思是 BlogPost
只能用來作為泛型約束,因為它有 Self
或者有關聯類型要求。查看 Equatable
的定義,確實是有 Self
:
public protocol Equatable {
static func == (lhs: Self, rhs: Self) -> Bool
}
具有關聯類型的協議不是類型,即使他們看起來是類型。而它們更像是類型占位符,這個類型可以是任何實現了我這個協議的類型。
在 Swift 5.1 中,我們就可以在返回值類型前面加上 some
來解決這個問題。把 createBlogPost()
方法改為:
func createBlogPost(title: String, author: String) -> some BlogPost {
guard !title.isEmpty && !author.isEmpty else {
fatalError("No title and/or author assigned!")
}
return Tutorial(title: title, author: author)
}
some
的作用就是告訴編譯器我這個方法的返回值可以是實現了 BlogPost
的任何類型。
修改完成之后,我們直接用 ==
比較 swift4Tutorial
和 swift5Tutorial
就不會報錯了。
在 SwiftUI 中,就是使用了 some
:
struct ContentView: View {
var body: some View {
Text("Hello World")
}
}
隱式返回
在 Swift 5.1 中,如果方法體只有一行語句,則可以省略 return
:
func myName() -> String {
"Lebron"
}
屬性包裝器
在 Swift 5.1 之前,使用計算屬性時,可能出現類似下面的代碼:
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
上面的代碼中,如果有更多的計算屬性,那么就要寫跟多的重復代碼。為了解決這個問題,Swift 5.1 引入了屬性包裝器,可以把上面的代碼簡寫為:
var settings = ["swift": true, "latestVersion": true]
@propertyWrapper
struct SettingsWrapper {
let key: String
let defaultValue: Bool
var wrappedValue: Bool {
get {
settings[key] ?? defaultValue
}
set {
settings[key] = newValue
}
}
}
struct Settings {
@SettingsWrapper(key: "swift", defaultValue: false) var isSwift: Bool
@SettingsWrapper(key: "latestVersion", defaultValue: false) var isLatestVersion: Bool
}
-
@propertyWrapper
把SettingsWrapper
標記為屬性包裝器。作為一個屬性包裝器,必須有一個名為wrappedValue
的屬性。 - 使用
@SettingsWrapper
標記Settings
中對應的屬性。
在 struct 中定義屬性的默認值
在 Swift 5.1 前,如果想要給 struct 的屬性定義默認值,必須這么寫:
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")
而在 Swift 5.1 以后,可以直接像 class 那樣給屬性定義默認值:
struct Author {
let name: String
var tutorialCount = 0
}
使用 Self
調用靜態成員
在 Swift 5.1 以前,需要使用 類名.靜態成員
來調用靜態成員:
struct Editor {
static func reviewGuidelines() {
print("Review editing guidelines.")
}
func edit() {
Editor.reviewGuidelines()
print("Ready for editing!")
}
}
而在 Swift 5.1 中,可以直接用 Self.靜態成員
:
struct Editor {
static func reviewGuidelines() {
print("Review editing guidelines.")
}
func edit() {
Self.reviewGuidelines()
print("Ready for editing!")
}
}
創建未初始化的數組
Swift 5.1 給 Array
添加了一個新的初始化方法:init(unsafeUninitializedCapacity:initializingWith:)
let randomSwitches = Array<String>(unsafeUninitializedCapacity: 5) { buffer, count in
for i in 0..<5 {
buffer[i] = Bool.random() ? "on" : "off"
}
// 必須給 `count` 賦值,否則 `randomSwitches` 會變成空數組
count = 5
}
static
和 class
下標
在 Swift 5.1 中可以定義 static
和 class
下標:
@dynamicMemberLookup
class File {
let name: String
init(name: String) {
self.name = name
}
// 定義 static 下標
static subscript(key: String) -> String {
switch key {
case "path":
return "custom path"
default:
return "default path"
}
}
// 使用 Dynamic Member Lookup 重寫上面的下標
class subscript(dynamicMember key: String) -> String {
switch key {
case "path":
return "custom path"
default:
return "default path"
}
}
}
File["path"] // "custom path"
File["PATH"] // "default path"
File.path // "custom path"
File.PATH // "default path"
用 @dynamicMemberLookup
標記 File
是為了可以使用點語法來訪問自定義的下標。
Keypath 支持動態成員查找
struct Point {
let x, y: Int
}
@dynamicMemberLookup
struct Circle<T> {
let center: T
let radius: Int
// 定義泛型下標,可以用 keypath 來訪問 `center` 的屬性
subscript<U>(dynamicMember keyPath: KeyPath<T, U>) -> U {
center[keyPath: keyPath]
}
}
let center = Point(x: 1, y: 2)
let circle = Circle(center: center, radius: 1)
circle.x // 1
circle.y // 2
Tuple 支持 Keypath
struct Instrument {
let brand: String
let year: Int
let details: (type: String, pitch: String)
}
let instrument = Instrument(
brand: "Roland",
year: 2019,
details: (type: "acoustic", pitch: "C")
)
// 使用 keypath 訪問 tuple
let type = instrument[keyPath: \Instrument.details.type]
let pitch = instrument[keyPath: \Instrument.details.pitch]
weak
和 unowned
屬性自動實現 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)
}
}
struct Tune: Hashable {
unowned let key: Key
weak var chord: Chord?
}
在 Swift 5.1 以前,Tune
的定義里面會報錯:沒有實現 Equatable
和 Hashable
;在 Swift 5.1 則已經自動實現。
不明確的枚舉 case
如果有不明確的枚舉 case,在 Swift 5.1 中會產生警告??。
enum TutorialStyle {
case cookbook, stepByStep, none
}
// 會產生警告
let style: TutorialStyle? = .none
因為 style
是 Optional
類型,編譯器不知道 .none
是 Optional.none
還是 TutorialStyle.none
,所有要寫具體一點,例如:let style: TutorialStyle? = TutorialStyle.none
匹配可選類型的枚舉
在 Swift 5.1 以前,switch
語句中匹配可選類型的枚舉時,case 后面需要加問號:
enum TutorialStatus {
case written, edited, published
}
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
}
Tuple 類型的轉換
let temperatures: (Int, Int) = (25, 30)
let convertedTemperatures: (Int?, Any) = temperatures
在 Swift 5.1 以前,(Int, Int)
是不能轉換成 (Int?, Any)
的,而在 Swift 5.1 可以。
Any 和泛型參數的方法重載
func showInfo(_: Any) -> String {
return "Any value"
}
func showInfo<T>(_: T) -> String {
return "Generic value"
}
showInfo("Swift")
在 Swift 5.1 以前,showInfo("Swift")
返回的是 Any value
;而在 Swift 5.1 中,返回的是 Generic value
。也就是說在 Swift 5.1 以前,Any 參數類型的方法優先;而在 Swift 5.1 中,泛型參數的方法優先。
autoclosure 參數可以定義別名
在 Swift 5.1 中,@autoclosure
標記的 closure 參數可以使用別名:
struct Closure<T> {
typealias ClosureType = () -> T
func apply(closure: @autoclosure ClosureType) {
closure()
}
}
在 Swift 5.1 以前,只能這樣寫:
struct Closure<T> {
func apply(closure: @autoclosure () -> T) {
closure()
}
}
在 Objective-C 方法中返回 self
在 Swift 5.1 以前,被 @objc
標記的方法中返回 self
,必須繼承自 NSObject
:
class Clone: NSObject {
@objc func clone() -> Self {
return self
}
}
在 Swift 5.1 中,則無需集成 NSObject
class Clone {
@objc func clone() -> Self {
self
}
}
完
想及時看到我的新文章的,可以關注我。同時也歡迎加入我管理的Swift開發群:536353151
。