Swift 5.5引入了一个名为"有效只读属性 "的新概念,这基本上意味着计算属性在计算其值时可以利用错误和异步操作等控制流机制。
抛出属性
让我们先来看看计算属性现在如何使用Swift的标准错误处理机制来抛出错误。举个例子,假设我们现在使用内置的 Result类型的get 方法来提取一个结果的封装值,或者抛出它包含的任何错误。
func handleLoginResult(_ result: Result<User, Error>) throws {
let user = try result.get()
...
}
由于那个get 方法实际上并不执行任何种类的工作,而只是让我们使用try 关键字来解压一个Result 的值,所以它现在也可以被声明为一个属性--例如像这样:
extension Result {
var value: Success {
get throws { try get() }
}
}
有了上面的内容,我们现在可以通过访问我们新的value 属性,从任何Result 实例中检索基础值,只要我们在这些表达式前加上try ,并处理任何可能被抛出的错误--就像直接使用get 方法时一样:
func handleLoginResult(_ result: Result<User, Error>) throws {
let user = try result.value
...
}
虽然计算属性现在可以抛出错误这一事实在某些情况下会变得非常有用,但重要的是仍然要牢记属性和函数之间的语义差异。因此,虽然上面的value 属性作为一个属性可能是完全有意义的(因为它只是检索一个底层的值),但许多抛出操作可以说还是作为函数来实现更好。要了解我对这个问题的更多想法,请查看《Swift中的计算属性》。
异步属性
伴随着新的并发系统,Swift 5.5 也使计算属性完全异步化。与属性现在可以使用throws 关键字类似,任何用async 注解的属性现在都可以自由调用其他异步 API,而访问这种属性的代码将被系统暂停,直到所有这些底层异步操作完成。
例如,这里一个DatabaseEntity ,每当它的isSynced 属性被访问时,都会异步地与它的父Database 检查是否已经同步:
class DatabaseEntity {
var isSynced: Bool {
get async {
await database?.isEntitySynced(self) ?? false
}
}
private weak var database: Database?
...
}
当涉及到异步代码时,我们可以说必须更加谨慎地对待我们在基于属性的API后面实现的那种操作。例如,我们可能不想执行像网络调用这样的任务或其他相对较长的运行操作作为计算属性的一部分,但对于需要跳转到一个单独的调度队列的事情,或涉及某种形式的文件I/O的任务,async 标记的属性可能被证明是相当有用的。
协议要求
最后,让我们也快速看看这些新的 "有效属性 "在作为协议的一部分声明时是什么样子的。就像协议可以定义标准属性作为其需求列表的一部分一样,协议现在也可以要求这些属性是异步的,并且可以选择性地使它们抛出。
例如,如果我们把上面的isSynced 属性定义为协议的一部分,它可能是这样的:
protocol Syncable {
var isSynced: Bool { get async }
}
如果我们还想让符合Syncable 的类型抛出错误,作为其isSynced 实现的一部分,那么我们可以简单地在上述声明中添加throws 关键字--像这样:
protocol Syncable {
var isSynced: Bool { get async throws }
}
就像其他抛出协议的要求一样,throws 标记的计算属性的实现实际上并不需要抛出,他们只是有这样的选择。
总结
这就是对计算属性如何从Swift 5.5开始用throws 或async 关键字进行标记的简单介绍。
谢谢你的阅读!