- Async/await
- 是個啥
一言以蔽之, 以前需要用閉包回調(diào)來寫的代碼, 我們現(xiàn)在可以用async/await來寫, 這讓我們可以拋棄復(fù)雜的閉包嵌套代碼, 極大的簡化了代碼, 提升可讀性
舉個??
我們先查詢歷史天氣, 再計算出平均溫度, 最后上傳
func fetchWeatherHistory(completion: @escaping ([Double]) -> Void) {
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) {
DispatchQueue.global().async {
let total = records.reduce(0, +)
let average = total / Double(records.count)
completion(average)
}
}
func upload(result: Double, completion: @escaping (String) -> Void) {
DispatchQueue.global().async {
completion("OK")
}
}
調(diào)用的時候
fetchWeatherHistory { records in
calculateAverageTemperature(for: records) { average in
upload(result: average) { response in
print("Server response: \(response)")
}
}
}
可以發(fā)現(xiàn), 無論是寫起來還是閱讀都非常hard, 尤其對新手及其不友好
那么用了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"
}
可以看到, 非常清晰, 可讀性非常高
- async/await 也支持 try/catch
for example:
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!")
}
}
- 只讀屬性里也可以用
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)
}
- async let的使用
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"]
}
如果想通過這三個屬性構(gòu)造一個User對象, async let 將是最簡單的方法 -- 每個方法都是異步的, 等待這三個方法全部執(zhí)行完, 才會去構(gòu)建新對象
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 let 必須在聲明為async的context中, 如果缺少async標(biāo)記, 那么async let將不會等待結(jié)果產(chǎn)生, 直到離開當(dāng)前這個作用域
在那些會拋出異常的方法中, 我們也不必使用try加 async let, 因為一旦發(fā)生錯誤, 會直接轉(zhuǎn)到你await的那個結(jié)果處, 所以我們不用寫 try await someFunction(), 直接async let xx = someFunction()就完事!
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
}
這個例子中我們本來要寫 try await fibonacc(of:), 但是留到了最后寫
- 優(yōu)雅的處理外部傳入的閉包
比如在外部定義了這樣一個方法, 我們不方便直接改造成async await
func fetchLatestNews(completion: @escaping ([String]) -> Void) {
DispatchQueue.main.async {
completion(["Swift 5.5 release", "Apple acquires Apollo"])
}
}
我們可以用一個新的 fetchLatestNews() 方法將這個閉包包起來
func fetchLatestNews() async -> [String] {
await withCheckedContinuation { continuation in
fetchLatestNews { items in
continuation.resume(returning: items)
}
}
}
我們可以這樣調(diào)用了
func printNews() async {
let items = await fetchLatestNews()
for item in items {
print(item)
}
}
- 其他
- Actors
這段代碼有問題嗎?
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)
}
}
我們用 SafeCollector actor 來重寫 RiskyCollector
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)
}
}
值得注意以下幾點:
用actor關(guān)鍵字來構(gòu)建Actor, 就想class, struct 和 enum
send()方法被async標(biāo)記, 那么它會等待這個方法完成, 即transfer執(zhí)行完
雖然 transfer(card:) 沒有被async 標(biāo)記, 但是我們?nèi)孕枰谡{(diào)用的時候加上await, 讓他在下一個actor 能發(fā)起這個請求之前一直保持等待狀態(tài)
actor保證我們能夠隨意的異步使用屬性和方法, 當(dāng)然要保證被async修飾, 所有的 actor-isolated state 都能不會被異步同時訪問
actor和class的相同點:都是引用類型
都有方法, 屬性, 構(gòu)造方法, subcript
可以遵循協(xié)議, 可以generic
所有的方法和屬性都是static, 因為沒有self的概念
不同點:不能繼承, 所以我們構(gòu)造時也更加簡單, 不需要使用convience initializers, overriding, final等關(guān)鍵字
遵循了特有的Actor協(xié)議, 其他類型都不可以使用
Global actors
@MainActor global acto保證你只能在主線程訪問他
class OldDataController {
func save() -> Bool {
guard Thread.isMainThread else {
return false
}
print("Saving data…")
return true
}
}
使用@MainActor可以替代 DispatchQueue.main
class NewDataController {
@MainActor func save() {
print("Saving data…")
}
}
-
if 作為后綴表達(dá)式(postfix member expressions)
比如
Text("Welcome")
#if os(iOS)
.font(.largeTitle)
#else
.font(.headline)
#endif
還可以嵌套
#if os(iOS)
.font(.largeTitle)
#if DEBUG
.foregroundColor(.red)
#endif
#else
.font(.headline)
#endif
使用可以很廣泛
let result = [1, 2, 3]
#if os(iOS)
.count
#else
.reduce(0, +)
#endif
print(result)
- CGFloat 和 Double 可以互相轉(zhuǎn)換
let first: CGFloat = 42
let second: Double = 19
let result = first + second
print(result)
那么result是什么類型?
- Codable for enums with associated values
enum Weather: Codable {
case sun
case wind(speed: Int)
case rain(amount: Int, chance: Int)
}
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)")
}
- lazy可以使用在局部上下文中
在方法中
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()
那么打印結(jié)果是?
Before lazy
After lazy
In printGreeting()
Hello, Paul
因為swift只有在訪問greeting的時候才會執(zhí)行g(shù)reeting這個計算屬性
這個更新有助于幫助我們只在有需要的時候才執(zhí)行代碼, 我們可以先寫好計算的代碼, 但是只有在真正需要的時候才會去執(zhí)行
- 屬性包裝器(property wrappers)
擴(kuò)展了屬性包裝器, 使之可以作為參數(shù)應(yīng)用在方法或者閉包中
比如我們想要限制下面方法中的入?yún)⒌姆秶?/li>
func setScore1(to score: Int) {
print("Setting score to \(score)")
}
setScore1(to: 50)
setScore1(to: -50)
setScore1(to: 500)
可以這樣做
先定義一個屬性包裝器
@propertyWrapper
struct Clamped<T: Comparable> {
let wrappedValue: T
init(wrappedValue: T, range: ClosedRange<T>) {
self.wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
}
func setScore2(@Clamped(range: 0...100) to score: Int) {
print("Setting score to \(score)")
}
setScore2(to: 50)
setScore2(to: -50)
setScore2(to: 500)
那么打印結(jié)果分別為 50, 0, 100
- 擴(kuò)展靜態(tài)類型推斷
我們現(xiàn)在有個Theme協(xié)議
protocol Theme { }
struct LightTheme: Theme { }
struct DarkTheme: Theme { }
struct RainbowTheme: Theme { }
定義一個Screen協(xié)議, 有一個theme() 方法
protocol Screen { }
extension Screen {
func theme<T: Theme>(_ style: T) -> Screen {
print("Activating new theme!")
return self
}
}
創(chuàng)建一個Screen的實例
struct HomeScreen: Screen { }
現(xiàn)在我們可以通過LightTheme()在screen上設(shè)置lightTheme了
let lightScreen = HomeScreen().theme(LightTheme())
為了簡單點, 我們?yōu)門heme擴(kuò)展了一個static屬性 light
extension Theme where Self == LightTheme {
static var light: LightTheme { .init() }
}
但是因為靜態(tài)屬性的關(guān)系, 在協(xié)議方法theme()中, 每次都必須傳入LightTheme() 對象
但是在swift5.5, 我們就可以這樣寫了
let lightTheme = HomeScreen().theme(.light)
awsome!
參考鏈接: https://www.hackingwithswift.com/articles/233/whats-new-in-swift-5-5