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 的情况下完成获取和解码的工作。
暂无评论内容