Swift 5.1 新特性
Swift 5.1 內置于 Xcode 11,新增了很多新特性,比較重要的有以下幾個。
靜態成員的Self
Swift 5.1之后,可以使用Self
替代類名來訪問靜態成員。
class Student {
static var age = 10
static func study(){
print("Good Study")
}
func say(){
print(Self.age) // 訪問靜態屬性
Self.study() // 訪問靜態方法
}
}
let stu = Student()
stu.say()
Key Path元組
Swift 5.1 可以在Key Path
中使用元組,通過.
訪問元素。
struct Student {
var name: String
var age: Int
var score: (Swift: Double, iOS: Double, Mac: Double)
}
let stu = Student(name: "zhangsan", age: 20, score: (Swift: 88.0, iOS: 90.0, Mac: 95.0))
// 訪問元組的元素
let swift = stu[keyPath:\Student.score.Swift]
let ios = stu[keyPath:\Student.score.iOS]
let mac = stu[keyPath:\Student.score.Mac]
@dynamicMemberLookup查找Key Path
Swift 5.1 實現了@dynamicMemberLookup
查找Key Path
。
struct Person {
var name: String
var age: Int
}
@dynamicMemberLookup
struct Student<T> {
var stuNo: String
var person: T
// 實現方法
subscript<U>(dynamicMember member: KeyPath<T, U>) -> U {
person[keyPath: member]
}
}
let p = Person(name: "zhangsan", age: 20)
let stu = Student(stuNo: "123456789", person: p)
// 可以直接訪問 name 和 age 屬性
stu.name
stu.age
函數、閉包單表達式的隱式返回
解讀:如果一個閉包或者函數只包含一個返回表達式,那么可以把return
省略掉,隱式返回該表達式。
func add(a: Int, b: Int) -> Int {
a + b
}
add(a: 10, b: 20)
struct Rectangle {
var width = 0.0, height = 0.0
var area: Double {
width * height
}
}
根據默認值合成結構體的構造函數
解讀:以前一個結構體的所有屬性都有默認值時,編譯器會基于屬性生成兩個構造函數。
結構體名()
和結構體名(所有屬性參數)
,但是并不會生成可選屬性參數的構造函數,Swift 5.1 之后可以了。
struct Person {
var age = 0
var name = "zhangsan"
}
以下都是正確的構造函數
let zhangsan = Person()
let lisi = Person(age: 20, name: "lisi")
let wangwu = Person(name: "wangwu")
不透明的返回類型(Opaque Result Types)
一個案例引發的血案
// 報錯:Protocol 'Equatable' can only be used as a generic constraint because it has Self or associated type requirements
func generateIntNumber() -> Equatable {
return 5
}
let number1 = generateIntNumber()
let number2 = generateIntNumber()
// 報錯:Binary operator '==' cannot be applied to two 'Equatable' operands
if number1 == number2 {
print("numbers equal")
}
Swift5.1之前解決
// 使用泛型約束
func generateIntNumber<T: Equatable>() -> T {
// 強制轉換
return 5 as! T
}
let number1: Int = generateIntNumber()
let number2: Int = generateIntNumber()
if number1 == number2 {
print("numbers equal")
}
但如果此時把: Int
去掉,會發現依然報錯,因為返回的類型是一個不確定的T
。
Swift5.1解決
// 用some修飾,返回值的類型對編譯器就變成透明的了。在這個值使用的時候編譯器可以根據反回值進行類型推斷得到具體類型。
func generateIntNumber() -> some Equatable {
return 5
}
// 此時number1和number2的類型是some Equatable
let number1 = generateIntNumber()
let number2 = generateIntNumber()
if number1 == number2 {
print("numbers equal")
}
如果將generateIntNumber
改一下,又會出現問題:
// 報錯:Function declares an opaque return type, but the return statements in its body do not have matching underlying types
func generateIntNumber() -> some Equatable {
if Bool.random() {
return 5
}
else {
return "5"
}
}
// 此時number1和number2的類型是some Equatable
let number1 = generateIntNumber()
let number2 = generateIntNumber()
if number1 == number2 {
print("numbers equal")
}
概念
- 在保持性能的同時,隱藏真實類型的新功能。
- 通過引入
some
這個關鍵字去修飾返回值,語法上隱藏具體類型,所以叫做不透明結果類型,這樣可以讓被調用方選擇具體的返回值類型,并且是在編譯時確定下來的。 - 允許帶有
Self
或者associatedtype
的 protocol 作為返回類型。 - 再看一個案例
protocol Animal {
associatedtype Element
func feed(food: Element)
}
struct Cat: Animal {
typealias Element = String
func feed(food: String) {
print("Cat eat \(food)")
}
}
// 會發現這行報錯:Protocol 'Animal' can only be used as a generic constraint because it has Self or associated type requirements
//func makeAnimal() -> Animal {
// return Cat()
//}
// 從返回值看不出具體類型
func makeAnimal() -> some Animal {
return Cat()
}
let animal = makeAnimal()
type(of: animal)
作用
- SwiftUI 中廣泛使用
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello World")
}
}
屬性包裝類型(Property Wrapper Types)
基于 Swift 的 iOS 開發中,越來越多@
修飾的關鍵字出現,比如@UIApplicationMain
,在最新的 SwiftUI 框架中,會發現這樣的關鍵字越來越多,比如@State
,@Binding
,@EnvironmentObject
等,它們共同構成了 SwiftUI 數據流的基本單元,這些知識點會隨著學習 SwiftUI 而深入了解。
- 關鍵字
@propertyWrapper
,用它修飾一個結構體,它修飾的結構體可以變成一個新的修飾符并作用在其他代碼上,來改變這些代碼默認的行為。 - 用修飾符
@結構體名
去修飾其他的屬性,將屬性“包裹”起來,從而控制某個屬性的行為。屬性包裝類型名字由此而來。 - 案例
@propertyWrapper struct Trimmed {
private var value: String = ""
// 計算屬性
var wrappedValue: String {
get { value }
set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) }
}
init(wrappedValue initialValue: String) {
self.wrappedValue = initialValue
}
}
struct Post {
// 用上面定義的Trimmed修飾同類型的變量
@Trimmed var title: String
@Trimmed var body: String
}
// 任何字符串無論是在初始化期間還是通過后面的屬性訪問都會自動刪除前后面的空格。
var post = Post(title: " Swift Property Wrappers ", body: " is very important ")
post.title // "Swift Property Wrappers"
post.body // "is very important"
post.title = " @propertyWrapper "
post.title // "@propertyWrapper"
Swift 5.2 新特性
Swift 5.2 內置于 Xcode 11.4,Swift 5.2 并不像 Swift 5.1 那樣增加了很多新特性,主要有以下幾點更新。
將 Key Path 表達式作為函數
- 可以像調用函數一樣使用關鍵路徑表達式。
- 配合高階函數可以進一步簡化代碼。
- 適用于結構體與類。
// 結構體
struct Student {
var stuName: String
var stuClass: String
var stuAge: Int
var canVote:Bool {
return stuAge > 18
}
}
// 構造三個實例并放入數組
let zhangsan = Student(stuName: "張三", stuClass: "移動互聯應用技術", stuAge: 17)
let lisi = Student(stuName: "李四", stuClass: "云計算技術與應用", stuAge: 18)
let wangwu = Student(stuName: "王五", stuClass: "大數據技術與應用", stuAge: 19)
let stus = [zhangsan, lisi, wangwu]
// 1. 獲取所有人的名字
let stuNames = stus.map(\.stuName)
// ["張三", "李四", "王五"]
// 2. 篩選年齡達到可以投票的學生
let stuNo = stus.filter(\.canVote)
// [{stuName "王五", stuClass "大數據技術與應用", stuAge 19}]
可調用類型
- 如果一個值是通過類型中名為
callAsFunction
的方法實現的,那么可以直接通過語法該類型的實例()
獲取該值。 - 適用于結構體與類。
// 結構體
struct Student {
var stuName: String
var stuClass: String
var stuAge: Int
// 聲明一個callAsFunction的函數,返回值根據需要調整
func callAsFunction() -> String {
if stuAge > 18 {
return "具有投票權"
}
return "不具有投票權"
}
}
let zhangsan = Student(stuName: "張三", stuClass: "移動互聯應用技術", stuAge: 17)
// 直接通過對象調用
print(zhangsan()) // "不具有投票權",等價于 zhangsan.callAsFunction()
let lisi = Student(stuName: "李四", stuClass: "云計算技術與應用", stuAge: 19)
print(lisi()) // "具有投票權"
新的改進的診斷體系
引入了一種新的診斷體系結構來提高 Xcode 發出的錯誤消息的質量和精度。這在使用 SwiftUI 編寫代碼時尤其明顯,寫過 SwiftUI 的都知道經常 Xcode 發出的錯誤信息經常是不準確的。
Swift5.2之前
import SwiftUI
struct ContentView: View {
@State private var name = 0
var body: some View {
HStack {
Text("姓名")
TextField("請輸入姓名", text: $name)
.textFieldStyle(RoundedBorderTextFieldStyle())
// Swift 5.2之前會提示下面一行有個錯誤
.frame(maxWidth: 300) // 錯誤信息:'Int' is not convertible to 'CGFloat?'
}
}
}
寫過 SwiftUI 的第一次看見這個錯誤肯定覺得奇怪(嗯?300 沒錯誤啊!),其實這并不是錯誤的真正原因。從語法來分析,錯誤的真正原因是TextField
需要綁定一個String
類型的Binding
值,而在定義的時候由于name
賦值為0
導致其類型為Int
,所以綁定值的類型不匹配才是真正的錯誤原因。
Swift5.2
import SwiftUI
struct ContentView: View {
@State private var name = 0
var body: some View {
HStack {
Text("姓名")
TextField("請輸入姓名", text: $name)
.textFieldStyle(RoundedBorderTextFieldStyle())
.frame(maxWidth: 300) // Swift 5.2的錯誤信息
// Cannot convert value of type 'Binding<Int>' to expected argument type 'Binding<String>'
}
}
}
很明顯,新的診斷體系結構給出的錯誤信息是準確的,這樣就可以快速定位 SwiftUI 的錯誤,提高開發的效率。