Swift 5.5中的新功能: 现在可以使用Swift的#if 编译器指令来有条件地编译后缀成员表达式。让我们来看看这个新功能在哪些情况下可以真正发挥作用。
围绕平台差异开展工作
虽然许多内置的API和框架在苹果的平台上以完全相同的方式工作,但有一些差异是我们可能需要解决的。例如,当使用SwiftUI构建一个在iOS和Mac上运行的应用程序时,我们可能会发现自己处于以下类型的情况--当我们试图将iOS特定的InsetGroupedListStyle 到一个List :
struct ItemList: View {
var items: [Item]
var body: some View {
List {
...
}
// Error: 'InsetGroupedListStyle' is unavailable in macOS
.listStyle(InsetGroupedListStyle())
}
}
一般来说,这类问题可以通过编译时的平台检查来解决--但在Swift 5.5之前,我们必须先将我们的List 分成一个单独的表达式,然后使用基于#if 的操作系统条件分别应用不同的listStyle 修改器:
struct ItemList: View {
var items: [Item]
var body: some View {
let list = List {
...
}
#if os(iOS)
list.listStyle(InsetGroupedListStyle())
#else
list.listStyle(InsetListStyle())
#endif
}
}
孤立地看,上面的代码看起来并不坏,但如果我们的ItemList 视图获得了额外的子视图(或者我们需要在其中执行额外的编译时检查),那么它的body 很快就会变得相当难读。
因此,也许解决这个问题的更稳健的方法是将上述平台检查提取到一个专门的修改器方法中--比如说像这样:
extension View {
func defaultListStyle() -> some View {
#if os(iOS)
listStyle(InsetGroupedListStyle())
#else
listStyle(InsetListStyle())
#endif
}
}
有了上述方法,我们可以简单地在我们的ItemList 视图中应用我们新的defaultListStyle 修改器,而在构建我们的实际用户界面时,我们不再需要处理任何平台差异:
struct ItemList: View {
var items: [Item]
var body: some View {
List {
...
}
.defaultListStyle()
}
}
然而,当我们希望在一个项目中多次重复使用修改器和样式时,上述方法当然是一种很好的技术,但每次遇到平台差异时,总是要定义一个专门的方法,会变得相当乏味。
这就是Swift 5.5对后缀成员表达式中#if 条件的新支持。
表达式中的内联检查
在使用 Swift 5.5 时,我们现在可以选择在表达式中直接内联#if 指令。因此,回到我们的ItemList ,我们现在可以完全内联地应用我们的每一个listStyle 修改器,而不必先把我们的表达式分成多个部分:
struct ItemList: View {
var items: [Item]
var body: some View {
List {
...
}
#if os(iOS)
.listStyle(.insetGrouped)
#else
.listStyle(.inset)
#endif
}
}
上面我们还利用了另一个新的语言特性,使我们能够使用点语法来引用SwiftUI列表样式(和其他类型的基于协议的类型)。
很好!当然,这项新功能也适用于其他类型的基于#if 的编译时检查--包括使用标准DEBUG 标志来检查我们的应用程序是否使用其调试构建配置进行编译,以及我们可能定义的任何自定义编译器标志。
作为一个例子,这里我们可以使用一个自定义标志来有条件地显示我们一个视图的最终渲染尺寸:
struct DynamicIcon: View {
var name: String
var body: some View {
Image(systemName: name)
.resizable()
.aspectRatio(contentMode: .fit)
#if SHOW_ICON_SIZES
.overlay(GeometryReader { geo in
Text("\(Int(geo.size.width)) x \(Int(geo.size.height))")
.font(.footnote)
.background(Color.blue)
.foregroundColor(.white)
})
#endif
}
}
总结
那么,我们应该如何在这些新的内联#if 条件与创建专门的修改器之间进行选择,以解决平台差异和执行其他类型的编译时检查?虽然每个开发者都会有自己的偏好,但对我来说,这完全取决于一个特定的模式是否会在代码库中重复出现,或者是否只执行一次。
对于重复的编译时检查,我仍然倾向于创建专门的函数,因为这可以让我把这些检查打包成一个更简单的API,但是当在小的平台差异中工作时(就像我们上面做的),我更喜欢新的内联风格。事实证明,在进行基于SwiftUI的跨平台项目时,它是相当有用的。
谢谢你的阅读!