概览
SwiftUI 的出现极大的解放了秃头码农们的生产力。SwiftUI 中众多原生和自定义视图对于我们创建精彩撩人的 App 功不可没!
不过,倘若小伙伴们略微留意过 SwiftUI 框架头文件里的源代码,就会发现里面嵌有一些奇怪 Never 类型,带来阵阵“违和感”:
那么 Never 到底是一种怎样的存在?它们在 SwiftUI 中又到底扮演着什么角色呢?
在本篇博文中,您将学到如下内容:
- 概览
- 1. 莫名其妙的”Never“!?
- 2. 什么!Never 竟然是一种”视图“?
- 3. Never 在 SwiftUI 视图中的作用
- 4. 尝试创建一个自定义原生 “Never” SwiftUI 视图
- 总结
闲言少叙,Let‘s find out!!!😉
1. 莫名其妙的”Never“!?
各位小伙伴们可能会奇怪 Never 到底表示什么?如果没记错的话,Never 的定义早在 SwiftUI 之前就已是 Swift(3.0)里的“囊中之物”了:
从 官方代码的注释中可以清楚的看到 Never 存在的意义:
/// The return type of functions that do not return normally, that is, a type
/// with no values.
///
/// Use `Never` as the return type when declaring a closure, function, or
/// method that unconditionally throws an error, traps, or otherwise does
/// not terminate.
///
/// func crashAndBurn() -> Never {
/// fatalError("Something very, very bad happened")
/// }
由上可知,Never 在 Swift 中主要有两种用途:
- 表示非正常返回方法(或函数、闭包)的返回类型(比如抛出异常、断言等);
- 表示没有值的类型;
比如,虽然和实际返回类型不一致,下面的 test 和 otherTest 方法都在某些错误条件下“返回” 了 Never 值:
func test(a: Int, b: Int) -> Int {
guard b != 0 else { fatalError()}
return a/b
}
func otherTest(items: [String]) -> [String] {
guard !items.isEmpty else { preconditionFailure("不能为空!") }
return items.map { $0.debugDescription }
}
test(a: 10, b: 0)
otherTest(items: [])
通过查看 fatalError() 和 preconditionFailure() 函数的定义,我们发现它们哥俩都会返回 Never:
public func fatalError(_ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line) -> Never
public func preconditionFailure(_ message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line) -> Never
同样,Never 也可以用来表示某种类型“不存在”的值(注意这种不存在和 nil 并不相同):
let p = PassthroughSubject<Int,Never>()
如上代码所示,我们定义的 PassthroughSubject 发布器永远不会发生错误(其错误类型为 Never)!
经过上面的讨论我们可以发现,Never 的作用比想象的要大的多!
值得注意的是,作为枚举类型的 Never 不能被实例化(至少我们从外部不能),我们只能“享用”它们现成的实例。
那么,Never 和 SwiftUI 又有怎样的关系呢?
2. 什么!Never 竟然是一种”视图“?
是滴,你没看错,Never 在 SwiftUI 中做了扩展,它确实可以表示为一种“视图”类型:
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension Never : View {
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension Never {
/// The type for the internal content of this `AccessibilityRotorContent`.
public typealias Body = Never
/// The internal content of this `AccessibilityRotorContent`.
public var body: Never { get }
}
比如,我们耳熟能详的 VStack 定义中就有 Never 可爱的身影:
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@frozen public struct VStack<Content> : View where Content : View {
@inlinable public init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content)
public typealias Body = Never
}
而且,Never 在 SwiftUI 不仅是一种视图,它还可以是一种 ShapeStyle、
TableColumnContent、Gesture 甚至一种 Scene:
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension Never : ShapeStyle {
/// The type of shape style this will resolve to.
///
/// When you create a custom shape style, Swift infers this type
/// from your implementation of the required `resolve` function.
public typealias Resolved = Never
}
@available(iOS 16.0, macOS 12.0, *)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
extension Never : TableColumnContent {
/// The type of sort comparator associated with this table column content.
public typealias TableColumnSortComparator = Never
/// The type of content representing the body of this table column content.
public typealias TableColumnBody = Never
/// The composition of content that comprise the table column content.
public var tableColumnBody: Never { get }
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension Never : Gesture {
/// The type representing the gesture's value.
public typealias Value = Never
}
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
extension Never : Scene {
}
看到这里,小伙伴呢可能感觉有些“头晕目眩”。SwiftUI 里搞这么多 Never 到底是要闹哪样呢?
3. Never 在 SwiftUI 视图中的作用
虽然很多小伙伴们都早已对 SwiftUI 撸码驾轻就熟,不过大家有没有考虑过这样一个问题:我们知道每个 View 都有一个 Body(类型为 some View),“爷爷”视图的 Body 是“爸爸”视图,而“爸爸”视图的 Body 是“儿子”视图…这样下去会出现“子子孙孙无穷尽”的情况,最终总要有一个最后的视图啊!
比如,观察下面的代码:
struct Text: View {
var body: some View {
???
}
}
struct Son: View {
var body: some View {
Text("Son")
}
}
struct Baba: View {
var body: some View {
Son()
}
}
其中,Baba 的 Body 中是 Son 视图,而 Son 的 Body 嵌入的是 Text 视图,那么 Text 里面又该怎么实现呢?我们假设 Text 里面还有一个 InnerText 视图,那么 InnterText 里又该如何?
这就是 Never 在 SwiftUI 中存在的绝佳意义:它终结了上面这种无穷尽的视图 Body 链!
那么,视图嵌套到底在哪里终结呢?答案就是在 SwiftUI 内置的原生视图里。
比如在上面的例子中,最终 Son 中里面是一个 Text 视图,大家都知道 Text 视图是 SwiftUI 提供的众多原生视图之一。从码农的角度来看它不能再被分解,从某种意义上可以认为它是一个“原子”视图:
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension Text : View {
/// The type of view representing the body of this view.
///
/// When you create a custom view, Swift infers this type from your
/// implementation of the required ``View/body-swift.property`` property.
public typealias Body = Never
}
看到了吗?SwiftUI 框架头文件里将 Text 的 Body 类型定义为了 Never,正是这一个小小的 Never 将我们从“无穷无尽”中解脱出来,整个世界变得清净了…
综上所述,SwiftUI 不可能永远询问嵌套视图的 Body,它需要特殊的视图,比如那些“原子”视图作为“终结者”,这样 SwiftUI 就可以停止“刨根问底”了。
我知道小伙伴们看到这里肯定会想:如果我们自己创建返回 Never 的自定义视图会怎样呢?
好吧,下面就满足你们的“痴心妄想”!😃
4. 尝试创建一个自定义原生 “Never” SwiftUI 视图
自己创建一个返回 Never 的 SwiftUI 视图很简单,简直轻而易举:
struct ImpossibleView: View {
var body: Never {
fatalError("感受一下炸弹的威力 💥")
}
}
编译没有任何问题,但是运行呢?
可以看到,不出所料 App 在启动时被毅然决然的 Crash 掉了,提示:
SwiftUI/DynamicProperty.swift:338: Fatal error: ImpossibleView may not have Body == Never
这是编译器在抗议:视图的 Body 绝对不能为 Never 类型!注意,出错信息并不是我们期望的 “感受一下炸弹的威力 💥”。
所以小伙伴们死心了吗?将 Never 作为 Body 类型是 SwiftUI 内置原生视图“神圣而不可侵犯”的特权!SwiftUI 在内部一定做了什么可以让原生视图“肆无忌惮”,我们秃头码农只能在外面“干瞪眼”了。
至此,我们彻底搞清楚了 Never 在 SwiftUI 中的“真正使命”,大家的 SwiftUI 内功又更精进了一层,棒棒哒!💯
总结
在本篇博文中,我们先是讨论了 Swift 语言中 Never 类型的起源,以及 Never 在 SwiftUI 中的“真正使命”,最后我们尝试了创建自己的 Never 视图。
感谢观赏,再会!😎