Async/await
新舊方式的比較
以前的方式:
func fetchWeatherHistory(completion: @escaping ([Double]) -> Void) {
// Complex networking code here; we'll just send back 100,000 random temperatures
DispatchQueue.global().async {
let results = (1...100_000).map { _ in Double.random(in: -10...30) }
completion(results)
}
}
func calculateAverageTemperature(for records: [Double], completion: @escaping (Double) -> Void) {
// Sum our array then divide by the array size
DispatchQueue.global().async {
let total = records.reduce(0, +)
let average = total / Double(records.count)
completion(average)
}
}
func upload(result: Double, completion: @escaping (String) -> Void) {
// More complex networking code; we'll just send back "OK"
DispatchQueue.global().async {
completion("OK")
}
}
現(xiàn)在的方式:
fetchWeatherHistory { records in
calculateAverageTemperature(for: records) { average in
upload(result: average) { response in
print("Server response: \(response)")
}
}
}
存在的問(wèn)題是
回調(diào)函數(shù)很容易調(diào)用多次,或者忘記調(diào)用。
函數(shù)參數(shù) @escaping (String) -> Void 看著也不直觀
“回調(diào)地獄”看起來(lái)也不美觀
在Swift 5.0 增加了Result 類型之前,返回錯(cuò)誤也困難。
Swift 5.5 Async/await方式
func fetchWeatherHistory() async -> [Double] {
(1...100_000).map { _ in Double.random(in: -10...30) }
}
func calculateAverageTemperature(for records: [Double]) async -> Double {
let total = records.reduce(0, +)
let average = total / Double(records.count)
return average
}
func upload(result: Double) async -> String {
"OK"
}
簡(jiǎn)單使用
func processWeather() async {
let records = await fetchWeatherHistory()
let average = await calculateAverageTemperature(for: records)
let response = await upload(result: average)
print("Server response: \(response)")
}
Async / await 錯(cuò)誤處理
swift 5.5 async 函數(shù)也可以像普通函數(shù)一樣拋出錯(cuò)誤 async throws。
enum UserError: Error {
case invalidCount, dataTooLong
}
func fetchUsers(count: Int) async throws -> [String] {
if count > 3 {
// Don't attempt to fetch too many users
throw UserError.invalidCount
}
// Complex networking code here; we'll just send back up to `count` users
return Array(["Antoni", "Karamo", "Tan"].prefix(count))
}
func save(users: [String]) async throws -> String {
let savedUsers = users.joined(separator: ",")
if savedUsers.count > 32 {
throw UserError.dataTooLong
} else {
// Actual saving code would go here
return "Saved \(savedUsers)!"
}
}
使用也是類似
func updateUsers() async {
do {
let users = try await fetchUsers(count: 3)
let result = try await save(users: users)
print(result)
} catch {
print("Oops!")
}
}
函數(shù)加上async 并不會(huì)自動(dòng)并行 concurrency
除非特殊處理,函數(shù)還是順序執(zhí)行,并不會(huì)自動(dòng)并行,更不會(huì)自動(dòng)跑在在其他線程。
Xcode 13 playground中運(yùn)行異步代碼
現(xiàn)在(2021-7-25)之前,暫時(shí)還沒(méi)有明顯優(yōu)雅的方式在playground中執(zhí)行async / await 代碼。參考這里,可以這樣執(zhí)行
import Foundation
struct Main {
static func main() async {
await doSomething()
}
static func doSomething() async {
print("doSomething")
}
}
Task.detached {
await Main.main()
exit(EXIT_SUCCESS)
}
RunLoop.main.run()
Async / await: sequences
SE-0298提案為swift 引入了AsyncSequence
protocol,循環(huán)異步隊(duì)列。 使用AsyncSequence
使用和Sequence
幾乎一樣。需要遵循AsyncSequence
和AsyncIterator
,當(dāng)然next()
方法需要時(shí)異步async
的,和Sequence
一樣,迭代結(jié)束確認(rèn)返回nil
。
struct DoubleGenerator: AsyncSequence {
typealias Element = Int
struct AsyncIterator: AsyncIteratorProtocol {
var current = 1
mutating func next() async -> Int? {
defer { current &*= 2 }
if current < 0 {
return nil
} else {
return current
}
}
}
func makeAsyncIterator() -> AsyncIterator {
AsyncIterator()
}
}
運(yùn)行也是常見(jiàn)的 for await
語(yǔ)法。
func printAllDoubles() async {
for await number in DoubleGenerator() {
print(number)
}
}
更高級(jí)的是,AsyncSequence
協(xié)議提供了一些常用的方法,像map()
, compactMap()
, allSatisfy()
等
func containsExactNumber() async {
let doubles = DoubleGenerator()
let match = await doubles.contains(16_777_216)
print(match)
}
read-only 屬性
SE-0310提案升級(jí)了swift的只讀屬性,讓其支持了async和throws。
enum FileError: Error {
case missing, unreadable
}
struct BundleFile {
let filename: String
var contents: String {
get async throws {
guard let url = Bundle.main.url(forResource: filename, withExtension: nil) else {
throw FileError.missing
}
do {
return try String(contentsOf: url)
} catch {
throw FileError.unreadable
}
}
}
}
使用自然像如下
func printHighScores() async throws {
let file = BundleFile(filename: "highscores")
try await print(file.contents)
}
Structured concurrency
SE-0304在async / await 和async sequence的基礎(chǔ)上為swift 引入了一整套的并發(fā)的執(zhí)行,取消,監(jiān)控的方法。 為了更好說(shuō)明,我們先假設(shè)這兩個(gè)情況
enum LocationError: Error {
case unknown
}
func getWeatherReadings(for location: String) async throws -> [Double] {
switch location {
case "London":
return (1...100).map { _ in Double.random(in: 6...26) }
case "Rome":
return (1...100).map { _ in Double.random(in: 10...32) }
case "San Francisco":
return (1...100).map { _ in Double.random(in: 12...20) }
default:
throw LocationError.unknown
}
}
func fibonacci(of number: Int) -> Int {
var first = 0
var second = 1
for _ in 0..<number {
let previous = first
first = second
second = previous + first
}
return first
}
在swift的項(xiàng)目中,可以這么執(zhí)行(如果在playground文件中,可以使用上文的方法)
@main
struct Main {
static func main() async throws {
let readings = try await getWeatherReadings(for: "London")
print("Readings are: \(readings)")
}
}
結(jié)構(gòu)化并發(fā),實(shí)際上是引入了兩個(gè)類型,Task
和TaskGroup
來(lái),獨(dú)立或者協(xié)同地運(yùn)行并發(fā)代碼。 簡(jiǎn)單來(lái)說(shuō),你只要將異步代碼傳入Task
對(duì)象,就會(huì)立即在background 線程上運(yùn)行,然后你用await
等待結(jié)果就好。
func printFibonacciSequence() async {
let task1 = Task { () -> [Int] in
var numbers = [Int]()
for i in 0..<50 {
let result = fibonacci(of: i)
numbers.append(result)
}
return numbers
}
let result1 = await task1.value
print("The first 50 numbers in the Fibonacci sequence are: \(result1)")
}
需要注意,代碼中明確指定了Task { () -> [Int] in
這樣,Swift就會(huì)知道task需要返回,而如果你的異步代碼比較簡(jiǎn)單,可以像下面這樣寫:
let task1 = Task {
(0..<50).map(fibonacci)
}
再次,task會(huì)在創(chuàng)建之后立即運(yùn)行,但是在斐波那契函數(shù)得到結(jié)果之后,,printFibonacciSequence()
函數(shù)會(huì)繼續(xù)運(yùn)行在原來(lái)的線程。
task
參數(shù)屬于non-escaping 閉包
主要到task的函數(shù)參數(shù)中并沒(méi)有標(biāo)注@escape
,因?yàn)閠ask會(huì)立即執(zhí)行傳入的函數(shù),而不是存儲(chǔ)然后之后執(zhí)行。因此,如果你在class
或者struct
中使用Task
,你不需要self
來(lái)獲取屬性和方法。
上述函數(shù)中,通過(guò)await task.value
來(lái)獲取task的值,如果你不關(guān)心task 返回的值,你也不需要存儲(chǔ)task。 對(duì)于會(huì)拋出錯(cuò)誤的異步任務(wù),從task的value
取值,也會(huì)觸發(fā)錯(cuò)誤,因此仍然需要try await
。
func runMultipleCalculations() async throws {
let task1 = Task {
(0..<50).map(fibonacci)
}
let task2 = Task {
try await getWeatherReadings(for: "Rome")
}
let result1 = await task1.value
let result2 = try await task2.value
print("The first 50 numbers in the Fibonacci sequence are: \(result1)")
print("Rome weather readings are: \(result2)")
}
Swift為task內(nèi)置了幾種優(yōu)先級(jí)high
, default
,low
和background
。如果未指定,會(huì)默認(rèn)設(shè)置為default
,當(dāng)然你可以顯式指定Task(priority: .high)。當(dāng)然如果你在Apple的平臺(tái)上,你會(huì)使用很熟悉的優(yōu)先級(jí),userInitiated
對(duì)應(yīng)high
,utility
對(duì)應(yīng)low
,當(dāng)然你不能使用userInteractive
,它是為主線程保留的。 當(dāng)然,Task也為我們提供了幾個(gè)靜態(tài)的方法。
-
Task.sleep()
會(huì)暫定當(dāng)前任務(wù)一定時(shí)間(納秒,也就是1_000_000_000為1秒) -
Task.checkCancellation()
會(huì)檢查當(dāng)前任務(wù)是否被cancel()取消,如果已經(jīng)取消了,會(huì)拋出CancellationError
錯(cuò)誤。 -
Task.yield()
會(huì)暫停當(dāng)前任務(wù)一定時(shí)間,讓給其他等待的任務(wù)讓出點(diǎn)時(shí)間,這點(diǎn)在循環(huán)做一些繁重的任務(wù)會(huì)很有用。
func cancelSleepingTask() async {
let task = Task { () -> String in
print("Starting")
await Task.sleep(1_000_000_000)
try Task.checkCancellation()
return "Done"
}
// The task has started, but we'll cancel it while it sleeps
task.cancel()
do {
let result = try await task.value
print("Result: \(result)")
} catch {
print("Task was cancelled.")
}
}
上述代碼中,``Task.checkCancellation()會(huì)檢測(cè)到task已經(jīng)被取消了,會(huì)立即跑出
CancellationError的錯(cuò)誤,當(dāng)然只有在嘗試
task.value取值的時(shí)候,才會(huì)拋出。
使用task.result
來(lái)獲取到Result
類型的值
你可以使用task.result
來(lái)獲取到Result
類型的值,上述代碼會(huì)返回Result<String, Error>
。當(dāng)然你也就不需要try
來(lái)捕捉錯(cuò)誤了。
對(duì)于復(fù)雜的任務(wù),可以使用 task group來(lái)組織更多的task。
func printMessage() async {
let string = await withTaskGroup(of: String.self) { group -> String in
group.async { "Hello" }
group.async { "From" }
group.async { "A" }
group.async { "Task" }
group.async { "Group" }
var collected = [String]()
for await value in group {
collected.append(value)
}
return collected.joined(separator: " ")
}
print(string)
}
不要將withTaskGroup
里面的代碼復(fù)制到外面,編輯不會(huì)報(bào)錯(cuò),但是自然會(huì)有潛在的問(wèn)題。 所有task group中的任務(wù)應(yīng)該返回同樣的數(shù)據(jù)類型。復(fù)雜情況,你可能需要一個(gè)有associated值的enum來(lái)準(zhǔn)確取值,當(dāng)然你還可以使用async let 綁定的替代方案。 task group的值需要所有的task都完成,但是每個(gè)task執(zhí)行完順序是不保證的。 你可以在task group中處理錯(cuò)誤,或者你可以使用withThrowingTaskGroup()
把錯(cuò)誤拋出,這樣也就需要try
的方式來(lái)取值。
func printAllWeatherReadings() async {
do {
print("Calculating average weather…")
let result = try await withThrowingTaskGroup(of: [Double].self) { group -> String in
group.async {
try await getWeatherReadings(for: "London")
}
group.async {
try await getWeatherReadings(for: "Rome")
}
group.async {
try await getWeatherReadings(for: "San Francisco")
}
// Convert our array of arrays into a single array of doubles
let allValues = try await group.reduce([], +)
// Calculate the mean average of all our doubles
let average = allValues.reduce(0, +) / Double(allValues.count)
return "Overall average temperature is \(average)"
}
print("Done! \(result)")
} catch {
print("Error calculating data.")
}
}
上述每個(gè) async
任務(wù),你可以簡(jiǎn)化使用for location in ["London", "Rome", "San Francisco"] {
來(lái)調(diào)用。 task group 提供一個(gè)cancelAll()
的方法來(lái)取消所有的task。之后你仍然可以給group添加異步任務(wù)。當(dāng)然,你可以使用asyncUnlessCancelled()
來(lái)跳過(guò)添加任務(wù),如果group已經(jīng)被取消—— 檢查Boolean的返回值類判斷group是否被取消。
async let 綁定
SE-0317引入了一種更簡(jiǎn)易的語(yǔ)法async let
來(lái)創(chuàng)建和等待子任務(wù)。這個(gè)可以作為task group的替代方法,特別是你需要使用不同數(shù)據(jù)類型的異步任務(wù)時(shí)候。
struct UserData {
let username: String
let friends: [String]
let highScores: [Int]
}
func getUser() async -> String {
"Taylor Swift"
}
func getHighScores() async -> [Int] {
[42, 23, 16, 15, 8, 4]
}
func getFriends() async -> [String] {
["Eric", "Maeve", "Otis"]
}
可以使用如下簡(jiǎn)單并發(fā)取值。
func printUserDetails() async {
async let username = getUser()
async let scores = getHighScores()
async let friends = getFriends()
let user = await UserData(name: username, friends: friends, highScores: scores)
print("Hello, my name is \(user.name), and I have \(user.friends.count) friends!")
}
你只能在async
的上下文中,使用async let
。
你只能在async
的上下文中,使用async let
。而且如果你不去使用await
取值,swift會(huì)在其作用于隱式等待。
綁定拋錯(cuò)的異步方法的時(shí)候,你也不需要使用try
關(guān)鍵詞。只需要取值時(shí)候try await
。 更高級(jí)的是,我們可以遞歸的使用async let語(yǔ)法。
enum NumberError: Error {
case outOfRange
}
func fibonacci(of number: Int) async throws -> Int {
if number < 0 || number > 22 {
throw NumberError.outOfRange
}
if number < 2 { return number }
async let first = fibonacci(of: number - 2)
async let second = fibonacci(of: number - 1)
return try await first + second
}
Continuation函數(shù)(轉(zhuǎn)換回調(diào)異步為async函數(shù))
SE-0300提供了把老的回調(diào)式異步函數(shù)轉(zhuǎn)換為async函數(shù)的方法。 例如,有如下的回調(diào)函數(shù)
func fetchLatestNews(completion: @escaping ([String]) -> Void) {
DispatchQueue.main.async {
completion(["Swift 5.5 release", "Apple acquires Apollo"])
}
}
swift 5.5之后,你不需要重寫你的所有代碼,你只需要使用withCheckedContinuation()
函數(shù)包裹就好。
func fetchLatestNews() async -> [String] {
await withCheckedContinuation { continuation in
fetchLatestNews { items in
continuation.resume(returning: items)
}
}
}
-
resume(returning:)
函數(shù)返回,你異步要返回的數(shù)據(jù)。 - 確保
resume(returning:)
函數(shù)只調(diào)用一次。在withCheckedContinuation()
函數(shù)中,swift會(huì)告警甚至?xí)罎⒋a,當(dāng)然這會(huì)有性能損耗。 - 如果有更高性能要求,或者你確保你的代碼不會(huì)有問(wèn)題,你可以使用``withUnsafeContinuation()
。
Actors
SE-0306引入了actor
,概念上和class
很相似,但是swfit確保了,actor中的變量在任意時(shí)間段內(nèi)只會(huì)被一個(gè)線程獲取,這也就確保了actor在并發(fā)環(huán)境下的安全。 例如下面的代碼
class RiskyCollector {
var deck: Set<String>
init(deck: Set<String>) {
self.deck = deck
}
func send(card selected: String, to person: RiskyCollector) -> Bool {
guard deck.contains(selected) else { return false }
deck.remove(selected)
person.transfer(card: selected)
return true
}
func transfer(card: String) {
deck.insert(card)
}
}
在單線程的環(huán)境中都是OK的。但是在多線程的環(huán)境中,我們代碼就有了潛在的資源競(jìng)爭(zhēng)風(fēng)險(xiǎn),這也就導(dǎo)致了,當(dāng)代碼并行運(yùn)行時(shí),代碼的執(zhí)行結(jié)果會(huì)可能不同。 假設(shè)我們調(diào)用send(card:to:)
在同一時(shí)間調(diào)用多次,
- 第一個(gè)線程檢查card是否在deck,存在,繼續(xù)
- 第二個(gè)線程也檢查card是否在deck,存在,也繼續(xù)
- 第一個(gè)線程刪除了deck中的card然后轉(zhuǎn)移給了第二個(gè)人。
- 第二個(gè)線程嘗試刪除deck中的card,但是實(shí)際上已經(jīng)不存在了,但是它還是把card轉(zhuǎn)移給了另一個(gè)人。
這樣就導(dǎo)致給一個(gè)人轉(zhuǎn)移了兩個(gè)卡片。絕對(duì)的麻煩。 Actor通過(guò)actor isolation隔離的方式解決這個(gè)問(wèn)題:
- 只能從外部異步地讀取到actor的屬性和方法,
- 不能從外部寫存儲(chǔ)后的屬性
swift 內(nèi)部通過(guò)隊(duì)列的方式避免資源競(jìng)爭(zhēng),因此應(yīng)能不會(huì)很好。 對(duì)于上述的例子,我們可以改寫為:
actor SafeCollector {
var deck: Set<String>
init(deck: Set<String>) {
self.deck = deck
}
func send(card selected: String, to person: SafeCollector) async -> Bool {
guard deck.contains(selected) else { return false }
deck.remove(selected)
await person.transfer(card: selected)
return true
}
func transfer(card: String) {
deck.insert(card)
}
}
- 通過(guò)
actor
關(guān)鍵詞來(lái)創(chuàng)建一個(gè)Actor,這個(gè)是swift 新加的類型。 -
send()
方法被標(biāo)為async
,因?yàn)樗枰欢〞r(shí)間來(lái)完成card轉(zhuǎn)移。 -
transfer(card:)
并沒(méi)有標(biāo)準(zhǔn)為async
,但是我們?nèi)匀恍枰?code>await 來(lái)調(diào)用,因?yàn)樾枰却?code>SafeCollector actor能夠處理請(qǐng)求。
更細(xì)節(jié)來(lái)說(shuō),actor內(nèi)部可以任意讀寫屬性和方法,但是和另一個(gè)actor交互的時(shí)候就必須使用異步的方式。這樣就保證了線程安全,而且更棒的事編譯后保證了這一點(diǎn)。 actor和class很像
- 都是引用類型,因此它們可以被用來(lái)分享狀態(tài)。
- 都有方法,屬性,構(gòu)造器,和下標(biāo)方法。
- 可以遵循協(xié)議和泛型
- 兩者靜態(tài)屬性和方法都是一樣的,因?yàn)樗鼈儧](méi)有
self
,因此也就不需要隔離。
當(dāng)然actor相比class有兩個(gè)最大的不同:
- Actor暫時(shí)不支持繼承,因此它們的構(gòu)造器initializer就簡(jiǎn)單多了——不需要convenience initializer,override,和
final
關(guān)鍵詞等。這個(gè)后續(xù)可能會(huì)改變。 - 所有的actor隱式的遵循了Actor的協(xié)議,其他的類型不能使用這個(gè)協(xié)議。
最好的actor
描述:“actors pass messages, not memory.”
actor不是直接訪問(wèn)別的acotr內(nèi)存,或者調(diào)用它們的方法,而是發(fā)送消息,讓swift runtime來(lái)安全處理數(shù)據(jù)。
Global actors
SE-0316引入了全局actor來(lái)隔離全局狀態(tài)避免數(shù)據(jù)競(jìng)爭(zhēng)。 目前來(lái)說(shuō)是引入了一個(gè)@MainActor
來(lái)標(biāo)柱裝飾你的屬性和方法,讓其保證只在主線程運(yùn)行。 對(duì)于app來(lái)說(shuō),UI更新就需要保證在主線程,以前的方式是使用DispatchQueue.main
。swift 5.5之后,就簡(jiǎn)單了
class NewDataController {
@MainActor func save() {
print("Saving data…")
}
}
@MainActor
標(biāo)柱之后,必須異步調(diào)用。
就像上文,這里實(shí)際上是actor
,因此,我們需要使用await
,async let
等來(lái)調(diào)用save()
@MainActor
底層是一個(gè)全局的actor
底層是MainActor
的結(jié)構(gòu)體。其中有一個(gè)靜態(tài)的run()
方法來(lái)讓我們代碼在主線程中執(zhí)行,而且也能夠返回執(zhí)行結(jié)果。
Sendable協(xié)議和@Sendable閉包
SE-0302支持了“可傳送”的數(shù)據(jù),也就是可以安全的向另一個(gè)線程傳送數(shù)據(jù)。也就是引入了新的Sendable
protocol和裝飾函數(shù)的@Sendable
屬性。 默認(rèn)線程安全的有
- 所有Swift核心的值類型,
Bool
,Int
,String
等 - 包裹的值類型的可選值(Optional)。
- swift標(biāo)準(zhǔn)庫(kù)值類型的集合,例如,
Array<String>
,Dictionary<Int, String>
。 - 值類型的元組(Tuple)
- 元類型(Metatype),例如
String.self
上述的值,都遵循了Sendable
的協(xié)議。 而對(duì)于自定義的類型,如果滿足下面的情況
-
Actor
自動(dòng)遵循Sendable
,因?yàn)閍ctor內(nèi)部異步的處理數(shù)據(jù)。 - 自定義的
struct
和enum
,如果它們只包含遵循Sendable
的值也會(huì)自動(dòng)遵循Sendable
,這點(diǎn)和Codable
很像。 -
class
能夠遵循Sendable
,如果1. 繼承自NSObject
,或者2. 不繼承,而且所有的屬性是常量的且能夠遵循Sendable
,還有類得標(biāo)注為final來(lái)防止將來(lái)的繼承。
Swift讓函數(shù)和閉包,標(biāo)注@Sendable,來(lái)能夠并發(fā)調(diào)用。例如,Task的初始化函數(shù)標(biāo)注了@Sendable,下面代碼能夠并發(fā)執(zhí)行,因?yàn)槠鋬?nèi)捕獲的是常量。
func printScore() async {
let score = 1
Task { print(score) }
Task { print(score) }
}
如果score
是變量,就不能在task
內(nèi)用了。
因?yàn)?code>Task會(huì)并發(fā)執(zhí)行,如果是變量,就存在數(shù)據(jù)競(jìng)爭(zhēng)了。
你可以在自己代碼標(biāo)注@Sendable
,這樣也會(huì)強(qiáng)制上述的規(guī)則(值捕獲)。
func runLater(_ function: @escaping @Sendable () -> Void) -> Void {
DispatchQueue.global().asyncAfter(deadline: .now() + 3, execute: function)
}
鏈?zhǔn)秸{(diào)用中支持#if語(yǔ)法
SE-0308中Swift支持了在鏈?zhǔn)秸{(diào)用( postfix member expression)使用#if
的條件判斷表達(dá)式。這個(gè)乍看有點(diǎn)費(fèi)解,其實(shí)是為了解決SwiftUI中根據(jù)條件添加view的修飾器。
Text("Welcome")
#if os(iOS)
.font(.largeTitle)
#else
.font(.headline)
#endif
支持嵌套調(diào)用
#if os(iOS)
.font(.largeTitle)
#if DEBUG
.foregroundColor(.red)
#endif
#else
.font(.headline)
#endif
當(dāng)然,也不是非得在Swift UI中使用
let result = [1, 2, 3]
#if os(iOS)
.count
#else
.reduce(0, +)
#endif
print(result)
#if只能用在.
操作
只有.
操作才算是postfix member expression,所以#if不能用在 +
,[]
等操作上。
CGFloat 和 Double 隱式轉(zhuǎn)換
SE-0307改進(jìn)為開(kāi)發(fā)帶來(lái)了巨大的便利:Swift 能夠在大多數(shù)情況下隱式轉(zhuǎn)換CGFloat
和Double
。
let first: CGFloat = 42
let second: Double = 19
let result = first + second
print(result)
Swift會(huì)有限使用Double
,更棒的是,不需要重寫原來(lái)的代碼,列入,Swift UI中的scaleEffect()
仍然可以使用CGFloat
,swift 內(nèi)部轉(zhuǎn)換為Double
。
Codable支持enum 關(guān)聯(lián)值
SE-0295升級(jí)了Swift Codable
,讓其能夠支持枚舉enum關(guān)聯(lián)值。之前只有遵循rawRepresentable
的enum才能使用Codable。
enum Weather: Codable {
case sun
case wind(speed: Int)
case rain(amount: Int, chance: Int)
}
現(xiàn)在能使用JSONEncoder的
let forecast: [Weather] = [
.sun,
.wind(speed: 10),
.sun,
.rain(amount: 5, chance: 50)
]
do {
let result = try JSONEncoder().encode(forecast)
let jsonString = String(decoding: result, as: UTF8.self)
print(jsonString)
} catch {
print("Encoding error: \(error.localizedDescription)")
}
// [{"sun":{}},{"wind":{"speed":10}},{"sun":{}},{"rain":{"amount":5,"chance":50}}]
上面json key 可以使用CodingKey來(lái)自定義。
函數(shù)中支持lazy關(guān)鍵詞
swift中lazy
關(guān)鍵詞能夠讓屬性延遲求值,現(xiàn)在swift 5.5之后,函數(shù)中也能使用lazy
關(guān)鍵詞了。
func printGreeting(to: String) -> String {
print("In printGreeting()")
return "Hello, \(to)"
}
func lazyTest() {
print("Before lazy")
lazy var greeting = printGreeting(to: "Paul")
print("After lazy")
print(greeting)
}
lazyTest()
property wrapper 可以裝飾到 function 和 closure 參數(shù)
SE-0293擴(kuò)展了property wrapper讓其能夠裝飾到函數(shù)和閉包參數(shù)。 例如原來(lái)的函數(shù)
func setScore1(to score: Int) {
print("Setting score to \(score)")
}
setScore1(to: 50)
setScore1(to: -50)
setScore1(to: 500)
// Setting score to 50
// Setting score to -50
// Setting score to 500
使用property wrapper可以,用來(lái)固定score的參數(shù)。
@propertyWrapper
struct Clamped<T: Comparable> {
let wrappedValue: T
init(wrappedValue: T, range: ClosedRange<T>) {
self.wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
}
現(xiàn)在使用的
func setScore2(@Clamped(range: 0...100) to score: Int) {
print("Setting score to \(score)")
}
setScore2(to: 50)
setScore2(to: -50)
setScore2(to: 500)
// Setting score to 50
// Setting score to 0
// Setting score to 100
擴(kuò)展泛型函數(shù)中協(xié)議靜態(tài)成員的查找
SE-0299 提升了Swift對(duì)泛型函數(shù)中協(xié)議靜態(tài)成員查找的能力。這點(diǎn)實(shí)際上也很能提升Swift UI的書(shū)寫的便利。 以前需要這么寫
Toggle("Example", isOn: .constant(true))
.toggleStyle(SwitchToggleStyle())
現(xiàn)在這么寫,就很方便
Toggle("Example", isOn: .constant(true))
.toggleStyle(.switch)
跟具體來(lái)說(shuō),假設(shè)我們有如下的協(xié)議和結(jié)構(gòu)體
protocol Theme { }
struct LightTheme: Theme { }
struct DarkTheme: Theme { }
struct RainbowTheme: Theme { }
我們?cè)俣x一個(gè)Screen
協(xié)議,有一個(gè)theme的泛型函數(shù),來(lái)設(shè)置主題。
protocol Screen { }
extension Screen {
func theme<T: Theme>(_ style: T) -> Screen {
print("Activating new theme!")
return self
}
}
現(xiàn)在我們有個(gè) Screen
的結(jié)構(gòu)體
struct HomeScreen: Screen { }
我們可以指定screen的主題為
let lightScreen = HomeScreen().theme(LightTheme())
現(xiàn)在Swift 5.5之后,我們可以在Theme
協(xié)議上加個(gè)靜態(tài)的屬性
extension Theme where Self == LightTheme {
static var light: LightTheme { .init() }
}
現(xiàn)在Swift 5.5 設(shè)置主題就簡(jiǎn)單了
let lightTheme = HomeScreen().theme(.light)