理解 SwiftUI大量使用泛型类型很重要。在 SwiftUI(和 Combine)发布之前,我从未见过如此大量使用泛型的 Swift 代码。 SwiftUI 中几乎所有符合View 的类型(和符合ViewModifier 的类型)都是泛型类型。
ViewModifier
那么,首先让我们谈谈ViewModifier。 ViewModifier 是一个协议。其他类型可以符合ViewModifier,但任何变量或值都不能只有普通类型ViewModifier。
为了使类型符合ViewModifier,我们定义了一个body 方法,该方法接受Content(无论是什么)并返回一个Body(无论是什么):
func body(content: Content) -> Body
ViewModifier 本质上只是一种方法,它将Content 作为输入并返回Body 作为输出。
Body 是什么? ViewModifier 将其定义为带有约束的associatedtype:
associatedtype Body : View
这意味着我们可以在ViewModifier 中选择称为Body 的特定类型,并且我们可以为Body 选择任何类型,只要它符合View 协议即可。
Content 是什么?文档告诉您它是typealias,这意味着我们可能无法选择它是什么。但是文档没有告诉你 Content 是什么别名,所以我们不知道 body 可以用它收到的 Content 做什么!
文档没有告诉您的原因是,如果符号以下划线 (_) 开头,Xcode 不会向您显示来自 SDK 的公共符号。但是您可以通过查看 SwiftUI 的 .swiftinterface 文件来查看 ViewModifier 的真实定义,包括隐藏符号。我解释了如何在this answer 中找到该文件。
查阅那个文件,我们找到了ViewModifier的真正定义:
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol ViewModifier {
static func _makeView(modifier: SwiftUI._GraphValue<Self>, inputs: SwiftUI._ViewInputs, body: @escaping (SwiftUI._Graph, SwiftUI._ViewInputs) -> SwiftUI._ViewOutputs) -> SwiftUI._ViewOutputs
static func _makeViewList(modifier: SwiftUI._GraphValue<Self>, inputs: SwiftUI._ViewListInputs, body: @escaping (SwiftUI._Graph, SwiftUI._ViewListInputs) -> SwiftUI._ViewListOutputs) -> SwiftUI._ViewListOutputs
associatedtype Body : SwiftUI.View
func body(content: Self.Content) -> Self.Body
typealias Content = SwiftUI._ViewModifier_Content<Self>
}
还有一些ViewModifier 的扩展定义了body、_makeView 和_makeViewList 的默认值,但我们可以忽略这些。
所以无论如何,我们可以看到Content 是_ViewModifier_Content<Self> 的别名,这是一个struct,它没有定义任何有趣的公共接口,但(在扩展中)符合View。所以这告诉我们,当我们编写自己的ViewModifier 时,我们的body 方法会收到某种View(具体类型由框架定义,我们可以直接称它为Content),然后返回某种View(我们可以选择特定的返回类型)。
这里有一个示例ViewModifier,我们可以将其应用于任何View。它填充修改后的视图并为其提供彩色背景:
struct MyModifier: ViewModifier {
var color: Color
func body(content: Content) -> some View {
return content.padding().background(color)
}
}
请注意,我们不必命名body 返回的View 的类型。我们可以使用some View,让Swift推导出具体的类型。
我们可以这样使用它:
Text("Hello").modifier(MyModifier(color: .red))
VStack
现在让我们谈谈VStack。 VStack 类型是 struct,而不是协议。它是通用的,这意味着它接受类型参数(就像函数接受函数参数一样)。 VStack 采用单个类型参数,名为 Content。这意味着VStack 定义了一个family 类型,每个类型对应一个它允许Content。
由于VStack 的Content 参数被约束为符合View,这意味着对于每个符合View 的类型,都有一个对应的VStack 类型。对于Text(符合View),有VStack<Text>。对于Image,有VStack<Image>。对于Color,有VStack<Color>。
但是我们通常不会拼出我们正在使用的VStack 的完整类型实例,而且我们通常不会将Content 类型设为像Text 或Image 这样的原始类型。使用VStack 的全部原因是在一个列中排列多个视图。 VStack 的使用告诉 Swift 垂直排列其子视图,VStack 的 Content 类型参数指定子视图的类型。
例如,当你这样写时:
VStack {
Text("Hello")
Button(action: {}) {
Text("Tap Me!")
}
}
您实际上是在创建这种类型的实例:
VStack<TupleView<(Text, Button<Text>)>>
这里的Content类型参数是TupleView<(Text, Button<Text>)>类型,它本身是一个泛型类型TupleView,有自己的类型参数名为T,而T这里是(Text, Button<Text>)(一个2元组,也称为一对)。所以类型的VStack 部分告诉SwiftUI 垂直排列子视图,TupleView<(Text, Button<Text>)> 部分告诉SwiftUI 有两个子视图:Text 和Button<Text>。
即使是这个简短的示例,您也可以看到如何生成具有多层嵌套泛型参数的类型。所以我们肯定想让编译器为我们找出这些类型。这就是 Apple 在 Swift 中添加 some View 语法的原因——这样我们就可以让编译器找出确切的类型。