学习 SwiftUI 肯定要看 View 协议,下面是 View 协议的相关代码。
1 | public protocol View { |
协议中, Body
是一个关联类型,由实例属性 body
推断出来的。 body
是一个被 @ViewBuilder
修饰的,可读的实例属性。感到疑惑的就是 some View 到底是个什么东西,以及 @ViewBuilder 是干嘛的。
读了翻译版 不透明类型 和原版 Opaque and Boxed Types 初步对不透明类型有了个大概了解。
不透明类型
不透明类型通常用于约束可读属性 或者 函数的返回值,隐藏类型信息,保证类型的一致性。
语法: : some 某协议名
,表示存在一种类型,该类型遵守某协议。
不透明类型的三大特征:
- 由实现者决定类型
- 类型一致,可以使用操作符。
- 返回值类型可以嵌套。
与泛型相比,泛型的类型由调用方指定,不透明类型则是由实现方决定。
与协议类型相比,不透明类型因为类型一致,可以使用 比较 等操作符。并且不透明类型作为返回类型可以嵌套。
比如:
1 | Rectangle() |
.fill .frame .border 的返回值类型都是 some View。
协议类型作为返回类型则不可以嵌套,详见 Swift 的一个官方说明 协议类型不可以遵守协议
所以 View 协议的 body 类型是 some View
。
那么为啥要用 @ViewBuilder
修饰呢? 这就要不得不说 some View 返回类型的一致性了。
1 | truct SomeView: View { |
比如在上面代码中,content 的 getter 中使用了 if 语句,但返回值都是 Text 类型,返回类型一致编译器不会报错。但如果改为下面的代码中,一个分支返回 Text 类型,一个分支返回 Button 类型就会报错了。
1 | // 报错: |
加上 @ViewBuilder 试一试,发现还是会报错。代码如下:
1 | struct SomeView: View { |
但是去掉各个分支中的 return 就不会报错了,代码如下:
1 | struct SomeView: View { |
@ViewBuilder
这用到了 Swift 语言的一个特性, Result builders 。
ViewBuilder 正是基于中 Result builders 这一特性,延伸出的专门用于构建视图的属性包装器。
上面代码中的 if else 返回两种类型视图,被 ViewBuilder 中的
static func buildEither<TrueContent, FalseContent>(second: FalseContent) -> _ConditionalContent<TrueContent, FalseContent> where TrueContent : View, FalseContent : View
封装为了 _ConditionalContent<TrueContent, FalseContent>
一个条件视图, 这就保证了类型的一致性。
ViewBuilder 还有适用于 body
中不同情况不同参数的其它方法,这里就不一一列举了。
小结
感觉 View 协议还有许多神秘的地方,这里仅仅是看到了暴漏出的 不透明类型 和 ViewBuilder,还有许多东西值得去探索。
参考
Protocol Types Cannot Conform to Protocols
Allowing self-conformance for protocols
The concepts behind SwiftUI: What is the keyword “some” doing?
Why does SwiftUI use “some View” for its view type?
Protocol doesn’t conform to itself?