- Async/await
- 是个啥
一言以蔽之, 以前需要用闭包回调来写的代码, 我们现在可以用async/await来写, 这让我们可以抛弃复杂的闭包嵌套代码, 极大的简化了代码, 提升可读性
举个🌰
我们先查询历史天气, 再计算出平均温度, 最后上传
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")
}
}
调用的时候
fetchWeatherHistory { records in
calculateAverageTemperature(for: records) { average in
upload(result: average) { response in
print("Server response: \(response)")
}
}
}
可以发现, 无论是写起来还是阅读都非常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"]
}
如果想通过这三个属性构造一个User对象, async let 将是最简单的方法 -- 每个方法都是异步的, 等待这三个方法全部执行完, 才会去构建新对象
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标记, 那么async let将不会等待结果产生, 直到离开当前这个作用域
在那些会抛出异常的方法中, 我们也不必使用try加 async let, 因为一旦发生错误, 会直接转到你await的那个结果处, 所以我们不用写 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:), 但是留到了最后写
- 优雅的处理外部传入的闭包
比如在外部定义了这样一个方法, 我们不方便直接改造成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)
}
}
}
我们可以这样调用了
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关键字来构建Actor, 就想class, struct 和 enum
-
send()方法被async标记, 那么它会等待这个方法完成, 即transfer执行完
-
虽然 transfer(card:) 没有被async 标记, 但是我们仍需要在调用的时候加上await, 让他在下一个actor 能发起这个请求之前一直保持等待状态
actor保证我们能够随意的异步使用属性和方法, 当然要保证被async修饰, 所有的 actor-isolated state 都能不会被异步同时访问
actor和class的相同点: -
都是引用类型
-
都有方法, 属性, 构造方法, subcript
-
可以遵循协议, 可以generic
-
所有的方法和属性都是static, 因为没有self的概念
不同点: -
不能继承, 所以我们构造时也更加简单, 不需要使用convience initializers, overriding, final等关键字
-
遵循了特有的Actor协议, 其他类型都不可以使用
-
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 作为后缀表达式(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 可以互相转换
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()
那么打印结果是?
Before lazy
After lazy
In printGreeting()
Hello, Paul
因为swift只有在访问greeting的时候才会执行greeting这个计算属性
这个更新有助于帮助我们只在有需要的时候才执行代码, 我们可以先写好计算的代码, 但是只有在真正需要的时候才会去执行
- 属性包装器(property wrappers)
扩展了属性包装器, 使之可以作为参数应用在方法或者闭包中
比如我们想要限制下面方法中的入参的范围
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)
那么打印结果分别为 50, 0, 100
- 扩展静态类型推断
我们现在有个Theme协议
protocol Theme { }
struct LightTheme: Theme { }
struct DarkTheme: Theme { }
struct RainbowTheme: Theme { }
定义一个Screen协议, 有一个theme() 方法
protocol Screen { }
extension Screen {
func theme<T: Theme>(_ style: T) -> Screen {
print("Activating new theme!")
return self
}
}
创建一个Screen的实例
struct HomeScreen: Screen { }
现在我们可以通过LightTheme()在screen上设置lightTheme了
let lightScreen = HomeScreen().theme(LightTheme())
为了简单点, 我们为Theme扩展了一个static属性 light
extension Theme where Self == LightTheme {
static var light: LightTheme { .init() }
}
但是因为静态属性的关系, 在协议方法theme()中, 每次都必须传入LightTheme() 对象
但是在swift5.5, 我们就可以这样写了
let lightTheme = HomeScreen().theme(.light)
awsome!
参考链接: https://www.hackingwithswift.com/articles/233/whats-new-in-swift-5-5
网友评论