每年,苹果的平台都以相当快的速度不断进步,但很多时候,我们也需要我们的应用程序支持它们所运行的各种操作系统的旧版本。因此,挑战就变成了--如何采用新的系统API和功能而不牺牲我们整体的向后兼容性?这就是可用性检查的作用。
可用性检查本质上使我们能够将一个函数、属性、类型或扩展标记为仅在某些平台和系统版本上可用。这反过来又让我们有条件地使用新的系统API和功能,同时还能让我们的其他代码继续在旧的系统版本上运行。
例如,下面的函数使用WidgetKit来重新加载应用程序的所有主屏幕部件,由于该API仅在iOS 14上可用,我们使用available 属性来使我们的应用程序仍然可以在iOS 13(或更早)上运行:
import WidgetKit
@available(iOS 14, *)
func reloadWidgets() {
WidgetCenter.shared.reloadAllTimelines()
}
我们的available 属性中的星,或星号,是用来代表 "所有未来版本 "的。因此,上述功能将自动在iOS 15,以及苹果公司未来发布的任何iOS版本上可用。
然而,虽然我们的应用程序本身现在可以继续在iOS 13和更早的版本上运行,但只有当我们的代码实际运行在iOS 14(或以上)时,我们才能调用我们的reloadWidgets 函数。因此,添加上述available 属性只是难题的第一部分。接下来,我们需要执行我们的实际可用性检查,这可以在我们调用我们的函数时使用#available 关键字来完成。
if #available(iOS 14, *) {
reloadWidgets()
}
由于上面只是一个标准的if 语句,我们也可以在上面附加其他的子句,比如一个else 块。在这种情况下,我们又回到了重新加载我们应用程序的传统 "今日视图 "小部件(这是在iOS 14中引入WidgetKit之前的小部件系统)中显示的数据。
if #available(iOS 14, *) {
reloadWidgets()
} else {
updateTodayViewData()
}
不过在这种情况下,我们可能不希望让每个调用站点来执行上述可用性检查,因为--如果我们在整个应用程序的多个地方重新加载我们的小工具数据--这将导致相当多的代码重复。
因此,另一个选择是在我们实际的reloadWidgets 函数中内联我们的可用性检查,而不是将其标记为仅适用于iOS 14。
func reloadWidgets() {
if #available(iOS 14, *) {
WidgetCenter.shared.reloadAllTimelines()
} else {
updateTodayViewWidgetData()
}
}
这样一来,我们就可以在任何我们想要的地方调用我们的函数,并且它会根据我们应用程序运行的系统版本自动调用正确的代码路径--很好!
在执行可用性检查时,也可以添加多个平台。例如,如果我们正在开发的应用程序同时支持iOS和macOS,那么我们可以通过这样做一次性检查iOS 14或macOS 11。
func reloadWidgets() {
if #available(iOS 14, macOS 11, *) {
WidgetCenter.shared.reloadAllTimelines()
} else {
updateTodayViewWidgetData()
}
}
available 属性也可以应用于整个类型或扩展,这在用自定义功能扩展某些需要最新操作系统版本的系统类型时特别有用。例如,在这里我们要扩展UIButton ,添加一些方法,使用iOS 15的新按钮配置API来应用某些样式。
@available(iOS 15, *)
extension UIButton {
func applyRoundedStyling() {
var config = UIButton.Configuration.filled()
config.background.backgroundColor = .systemBlue
config.cornerStyle = .medium
config.contentInsets = NSDirectionalEdgeInsets(
top: 10, leading: 8, bottom: 10, trailing: 8
)
configuration = config
}
func applyPrimaryStyling() {
...
}
func applySecondaryStyling() {
...
}
}
最后,让我们也来看看可用性检查如何在SwiftUI视图中使用。例如,假设我们正在开发一个ItemListView ,当它可用时(在iOS 14和更高版本),我们希望采用InsetGrouped 列表样式,而当它不可用时,则退回到GroupedListStyle 。
使用我们到目前为止所探索的技术,关于如何做到这一点的初步想法可能是在我们视图的body 内联添加一个#available 检查 - 像这样:
struct ItemList: View {
var items: [Item]
var body: some View {
let list = List(items) { item in
...
}
if #available(iOS 14, *) {
list.listStyle(InsetGroupedListStyle())
} else {
list.listStyle(GroupedListStyle())
}
}
}
然而,虽然上述方法可行,但可以说这不是一个非常优雅的解决方案,而且让我们更难了解我们的实际视图层次结构是什么样的。
所以,让我们用下面的模式来代替--这涉及到创建一个ViewBuilder-marked函数,让我们应用我们的默认列表样式,就像标准的SwiftUI修改器的应用方式。
extension View {
@ViewBuilder
func defaultListStyle() -> some View {
if #available(iOS 14, *) {
listStyle(InsetGroupedListStyle())
} else {
listStyle(GroupedListStyle())
}
}
}
有了上述内容,我们现在可以让我们的ItemList 视图(以及任何其他想要使用上述列表样式逻辑的视图)变得更加简单:
struct ItemList: View {
var items: [Item]
var body: some View {
List(items) { item in
...
}
.defaultListStyle()
}
}
这就是使用 Swift 的available 属性及其相关可用性检查的几种方法,无论是在 SwiftUI 的背景下,还是在任何其他 Swift 代码中。但值得指出的是,所有这些检查都是在运行时进行的,基于我们的应用所运行的设备的系统版本。为了代替编译时的检查,我们可以使用"在Swift中使用编译器指令 "中涉及的一些技术。
虽然Swift的可用性功能非常方便,而且可以让我们开始采用一些最新的系统功能,而不必增加我们应用程序的部署目标,但我真的建议少用它们。在代码库中散布大量的可用性检查往往会导致代码难以阅读和维护,所以只要有可能,将此类检查隔离在方便的API中(如我们的reloadWidgets 和defaultListStyle 函数)绝对是我的首选方法。
谢谢你的阅读!