swift12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061import SwiftUIstruct ContentView: View { var body: some View { Fibonacci(ordinal: 10) }}struct Fibonacci: View { var ordinal: Int private var value: Value init(ordinal: Int) { self.init(ordinal: ordinal, value: .zero) } private init(ordinal: Int, value: Value) { self.ordinal = ordinal self.value = value } var body: some View { if ordinal == 0 { Text("\(value.description)") } else { Fibonacci(ordinal: ordinal - 1, value: value.next()) } } private enum Value: CustomStringConvertible { case zero case one case more(last: Int, current: Int) func next() -> Value { switch self { case .zero: return .one case .one: return .more(last: 0, current: 1) case .more(let last, let current): return .more(last: current, current: last + current) } } var description: String { switch self { case .zero: return "0" case .one: return "1" case .more(let last, let current): return "\(last + current)" } } }}
swift12345678public protocol View { associatedtype Body: View @ViewBuilder var body: Body { get }}
我们可以看到,一旦一个类型遵从了 View 协议,它的实例将拥有一个 body 属性。同时这个 body 属性也将返回一个遵从 View 协议的实例。这将导致一个有趣的现象:如果我们获得了一个 View 实例的 body,那么我们将又能获得这个 View 实例 body 的 body。因为 View 实例 body 的 body 也是一个 View 实例,所以我们可以继续获得它的 body。理论上,我们可以无限重复这个模式。
SwiftUI 的执行模式
是的。你也许已经意识到了 View 协议的设计是递归的。实际上,这个递归的设计提供了 SwiftUI 的基本语言结构。而这个基本语言结构引入了一种可以递归的执行模式。SwiftUI 的内部机制可以持连不断地续获得 View 实例的 body 直到递归结束。但是什么时候递归结束呢?SwiftUI 扩展了标准库中的 Never 类型以遵从 View 协议。在这个扩展中,Never 的 body 同时会返回 Never。所以我们可以知道这就是递归的终点。
swift123456789extension Never: View { }extension Never { public typealias Body = Never public var body: Never { get }}
在 SwiftUI 的内部实现中,一旦一个 View 实例的 body 是 Never 类型的,SwiftUI 将停止获取这个 View 实例的 body,转而切换到这个视图的内建逻辑。如果这个视图的内建逻辑不会将执行转交给其他 View 实例,那么递归将终止。SwiftUI 带来了很多 body 是 Never 类型的 View 类型。由于这类 View 类型拥有其内建的逻辑,他们也被成为内建视图。在我上面展示的斐波那契数列的例子中, 因为 Text 的 body 是 Never 类型的,同时 Text 的内建逻辑不会将程序执行转交给其他实例,程序的递归将终止在 SwiftUI 执行到 Fibonacci body 中的 Text 视图时。
swift12345extension Text : View { public typealias Body = Never}