Why SwiftUI primitives have body type Never?
When defining a SwiftUI view, it comes automatically to write struct MyView: View
followed by var body: some View { ... }
. All our views are composed of other views. At some point SwiftUI needs to draw something on the screen:
when does it end? How does SwiftUI know that it has reached the bottom of a view hierarchy?
In this article, let's continue our exploration of SwiftUI's inner workings.
The View
protocol
Every SwiftUI view is a type conforming to the View
protocol:
public protocol View {
associatedtype Body: View
@ViewBuilder var body: Self.Body { get }
}
Since this is a Swift protocol, we can make any type conform to it, for example, String
:
extension String: View {
public var body: some View {
Text(self)
}
}
this is just an example, not a suggestion nor a best practice.
Which allows us to write a String
directly into a View
declaration:
struct ContentView: View {
var body: some View {
"Hello world!"
}
}
As we're using the Text
primitive in String
's body
, this will work as expected, but what if we didn't? Let's build a new SwiftUI view which uses none of SwiftUI's primitives:
struct MyView: View {
var body: some View {
self
}
}
In this case we're defining a new View
called MyView
, here we use it in ContentView
:
struct ContentView: View {
var body: some View {
MyView()
}
}
This builds fine. However, as MyView
's' body
is the view itself, we're stuck on an infinite recursion, which will make SwiftUI terminate our app.
Despite the View
protocol letting us conform pretty much anything to it, if we want to use those declarations in SwiftUI, we must use SwiftUI primitives or be ready to see our app crash.
What do these SwiftUI primitives have that make them unique, allowing SwiftUI to break the infinite recursion we've found ourselves in? The answer is in their body
type: Never
.
Never
Swift 3.0 has brought us Never
, an uninhabited type: Never
is a type with no possible values, making it impossible for us to get or create an instance.
We might have met Never
for example:
- in Combine's Publishers, typically to indicate that a given publisher can't throw errors
- as the return type of
preconditionFailure(_:file:line:)
orfatalError(_:file:line:)
, indicating that once called, there's no way out
Let's declare a view with body type Never
:
struct ImpossibleView: View {
var body: Never
}
This builds! However, we cannot use it: we can't instantiate it without passing something like fatalError()
. Regardless, let's implement the body and run our app:
struct ImpossibleView: View {
var body: Never {
fatalError("This will make our app 💥")
}
}
Here's our ContentView
:
struct ContentView: View {
var body: some View {
ImpossibleView()
}
}
Unsurprisingly, our app will crash once again, However, the crash reason is Fatal error: ImpossibleView may not have Body == Never: file SwiftUI, line 0
, not our This will make our app 💥
.
Reading throughout the stack trace, we will see an assertionFailure
within an internal SwiftUI's BodyAccessor.makeBody(container:inputs:fields:)
method, which is not happy with our ImpossibleView
body
type.
This is the same method that will crash our app if we pass a
class
instance instead of a value type.
Only views declared within SwiftUI are allowed to have body type Never
. Despite not having access to BodyAccessor
's code, it's clear that those views would either pass this assertion or that they'd take a different, special path.
SwiftUI can't keep asking for view bodies forever: it needs a special set of views, a.k.a. a set of primitives, that it can draw without asking for their body. This is why Text
, ZStack
, Color
, etc., have Never
as their body type.
Is Never
a View
?
A type conforming to View
needs to return a body that is also a View
: Never
is a view.
SwiftUI knows not to ask for the body of views with body
type Never
, either by crashing if it's not a primitive or doing something else otherwise. However, since we must make our code compile, SwiftUI needs to extend Never
to be a View
: the ultimate, impossible view.
To confirm this, we can inspect SwiftUI's headers, where we will find the following declaration (spread in a few places):
extension Never: View {
public typealias Body = Never
public var body: Never { get }
}
The SwiftUI team could have declared another special type to be used instead of Never
. However, I find this solution very elegant and perfectly fitting for the use case.
Conclusions
In this article, we've explored how SwiftUI breaks the infinite recursion challenge when drawing views and how it uses the special Swift's type Never
to achieve that elegantly.
I hope you've found this article helpful: please let me know if I've missed anything!
Thank you for reading, and stay tuned for more SwiftUI articles!
Bonus track
Since the article is about impossible views, just for fun, I want to share another completely legit and 100% crashing way to "build" views: declare nothing but any modifier.
struct ContentView: View {
var body: some View {
border(Color.black)
}
}
// or
struct ContentView: View {
var body: some View {
padding()
}
}
// or
struct ContentView: View {
var body: some View {
ignoresSafeArea()
}
}
// etc
So many possibilities! 💣
Are you aware of any other interesting ways to crash SwiftUI? I'd love to know!