How to create and use async properties 如何创建和使用异步属性

How to create and use async properties 如何创建和使用异步属性

温馨提示:本文最后更新于2025-02-07 10:12:59,某些文章具有时效性,若有错误或已失效,请在下方留言

Just as Swift’s functions can be asynchronous, computed properties can also be asynchronous: attempting to access them must also use await or similar, and may also need throws if errors can be thrown when computing the property.
就像 Swift 的函数可以是异步的一样,计算属性也可以是异步的:尝试访问它们也必须使用 await 或类似的东西,如果在计算属性时可能抛出错误,则可能还需要 throws

This is what allows things like the value property of Task to work – it’s a simple property, but we must access it using await because it might not have completed yet. If you look at the property in Xcode’s generated interface for Task, you’ll see it’s declared as this:
这就是允许 Task 的 value 属性工作的原因——这是一个简单的属性,但我们必须使用 await 访问它,因为它可能还没有完成。如果你查看 Xcode 为 Task 生成的接口中的属性,你会看到它被声明为:

public var value: Success { get async throws }

That means it has a getter than runs asynchronously and can throw errors.
这意味着它有一个异步运行的 getter,并且可能会引发错误。

Important: This is only possible on read-only computed properties – attempting to provide a setter will cause a compile error.
重要:这仅适用于只读计算属性 – 尝试提供 setter 将导致编译错误。

To demonstrate this, we could create a RemoteFile struct that stores a URL and a type that conforms to Decodable. This struct won’t actually fetch the URL when the struct is created, but will instead dynamically fetch the contents of the URL every time the property is requested so that we can update our UI dynamically.
为了演示这一点,我们可以创建一个 RemoteFile 结构体,用于存储符合 Decodable 的 URL 和类型。这个结构体在创建结构体时实际上不会获取 URL,而是在每次请求属性时动态获取 URL 的内容,以便我们可以动态地更新我们的 UI。

Tip: If you use URLSession.shared to fetch your data it will automatically be cached, so we’re going to create a custom URL session that always ignores local and remote caches to make sure our remote file is always fetched.
提示:如果你使用 URLSession.shared 来获取你的数据,它将被自动缓存,因此我们将创建一个自定义的 URL 会话,它总是忽略本地和远程缓存,以确保我们的远程文件总是被获取。

Here’s the code:  这是代码:

// First, a URLSession instance that never uses caches
extension URLSession {
    static let noCacheSession: URLSession = {
        let config = URLSessionConfiguration.default
        config.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
        return URLSession(configuration: config)
    }()
}

// Now our struct that will fetch and decode a URL every
// time we read its `contents` property
struct RemoteFile<T: Decodable> {
    let url: URL
    let type: T.Type

    var contents: T {
        get async throws {
            let (data, _) = try await URLSession.noCacheSession.data(from: url)
            return try JSONDecoder().decode(T.self, from: data)
        }
    }
}

So, we’re fetching the URL’s contents every time contents is accessed, as opposed to storing the URL’s contents when a RemoteFile instance is created. As a result, the property is marked both async and throws so that callers must use await or similar when accessing it.
因此,我们在每次访问contents时都会获取 URL 的内容,而不是在创建 RemoteFile 实例时存储 URL 的内容。因此,该属性同时标记为 async 和 throws,以便调用方在访问它时必须使用 await 或类似代码。

To try that out with some real SwiftUI code, we could write a view that fetches messages. We don’t ever want stale data, so we’re going to point our RemoteFile struct at a particular URL and tell it to expect an array of Message objects to come back, then let it take care of fetching and decoding those while also bypassing the URLSession cache:
要使用一些真实的 SwiftUI 代码进行尝试,我们可以编写一个获取消息的视图。我们从来不需要过时的数据,因此我们将 RemoteFile 结构体指向特定的 URL,并告诉它期望返回一组 Message 对象,然后让它负责获取和解码这些对象,同时绕过 URLSession 缓存:

// First, a URLSession instance that never uses caches
extension URLSession {
    static let noCacheSession: URLSession = {
        let config = URLSessionConfiguration.default
        config.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
        return URLSession(configuration: config)
    }()
}

// Now our struct that will fetch and decode a URL every
// time we read its `contents` property
struct RemoteFile<T: Decodable> {
    let url: URL
    let type: T.Type

    var contents: T {
        get async throws {
            let (data, _) = try await URLSession.noCacheSession.data(from: url)
            return try JSONDecoder().decode(T.self, from: data)
        }
    }
}

struct Message: Decodable, Identifiable {
    let id: Int
    var user: String
    var text: String
}

struct ContentView: View {
    let source = RemoteFile(url: URL(string: "https://hws.dev/inbox.json")!, type: [Message].self)
    @State private var messages = [Message]()

    var body: some View {
        NavigationStack {
            List(messages) { message in
                VStack(alignment: .leading) {
                    Text(message.user)
                        .font(.headline)
                    Text(message.text)
                }
            }
            .navigationTitle("Inbox")
            .toolbar {
                Button("Refresh", systemImage: "arrow.clockwise", action: refresh)
            }
            .onAppear(perform: refresh)
        }
    }

    func refresh() {
        Task {
            do {
                // Access the property asynchronously
                messages = try await source.contents
            } catch {
                print("Message update failed.")
            }
        }
    }
}

That call to source.contents is where the real action happens – it’s a property, yes, but it must also be accessed asynchronously so that it can do its work of fetching and decoding without blocking the UI.
对 source.contents 的调用是真正作发生的地方 – 它是一个属性,是的,但它也必须异步访问,以便它可以在不阻塞 UI 的情况下完成获取和解码的工作。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容