Four Years Ago
Four years ago today I was in Cupertino for the eve of what was supposed to be a small day of personal achievement. I was part of a special project for the fruit company that I can’t discuss. The kickoff was in the morning, November 4th. I scarcely contained my giddy excitement.
I spent the evening at a hotel nearby, watching the returns roll in. The fuck is all that red on the map. When denial turned into dawning realization, and dawning realization decayed into despair, I violently switched off the television. I needed a drink, and not to be alone.
The plan was simple: wander until I encountered a bar. Any bar. It didn’t take long to stumble into one. Cramped inside, a handful of patrons. The horror show glared at us from the TV hanging under a drop-tile ceiling. Inescapable. What’re you gonna do.
“Jack. Neat.”
One shot glass turned into two, then three. It wasn’t enough. I once got booted in the nuts with so much force that I was propelled into the air. This felt worse. It was like watching God die on live TV, a melancholy accident, a wave function collapsing the wrong direction.
I wasn’t alone. An assembly line of grief had gathered there, nursing our shared world-historical wound. Time slipped off on a conveyer belt of booze. The bar tender had stopped charging for drinks and was robotically refilling everyone’s shot glasses as soon as they’d emptied.
The hangover the next morning was like none before. The hazy California sky was corpse-blue and relentless. Our little fruit project group assembled and rolled into the parking lot at the mothership. Where was the joy I’d anticipated all these years?
Friendly corporate busy-bees escorted us through electronically sealed doors, through gray hallways, past giant portraits of products in brushed metal and curved glass, and into a windowless room. Not enough chairs, so I sat legs-crossed in the corner leaning against a couch.
The next four hours demanded more of me than any software project I’ve ever been on, not in itself, but in context. We weren’t even writing code that day, merely setting up accounts and emails and corporate network access. Mind-numbing work, but nearly impossible that morning.
The obscenity of the night before clouded my vision. The sensible pale walls and polite gray carpeted floors mixed with the mind-numbing dullness of awaiting two-factor authentication codes, all giving way to a pall of grief, every keystroke a miracle of survival instincts.
Four years later I’m girding myself for another boot to the nuts, tallying the liquor stocks in the house, keeping my mind’s eye on the shelf where the shot glass waits patiently for a call to arms. Let’s hope it remains there this time around.
(Originally a Twitter thread.)
Every Skill is a Liability
I think the hardest skill to learn in programming is recognizing when you’re not communicating enough.
We’re often the first people to spot change, to feel the rumble of trouble over the horizon, but we’re also inclined to reflect on approaching danger internally, as a puzzle to solve without assistance, rather than externally by sharing the burden with our team.
Every skill is also a liability. Programming is anticipating the future and solving problems before anyone else has to know that a problem would have occurred. Every day we practice that skill and get better at it, but sometimes the right call is to resist the urge to problem solve, to instead simply report on what I’ve seen, to let my team know that a problem is coming, and then wait for the team to collaboratively decide who’ll solve the problem, and how, and when.
I wish I knew a secret that unlocks this ability without effort, but I don’t. The only way I know of is to dedicate some utility thread of my mind that periodically checks in to see if there’s anything that pattern-matches against mistakes I’ve made in the past, and quickly course correct by communicating with the right people.
Dagmar Chili Pitas: The Official Unofficial Mirror
(UPDATE, OCTOBER 2023: DAGMAR CHILI PITAS AND DOXOWOX, IT’S OLDER SIBLING, NOW HAVE PROPERLY-HOSTED, REAL-ASS WEBSITES.
“…and with the microscopic diligence of a Leuwenhoeck submits to the inspection of a shivering world ninety-six facsimiles of magnified Arctic snow crystals.”
{}oe|e|ep[]
Have you ever heard of Dagmar Chili Pitas? It’s twenty years old. It, sadly, went offline a few years back. It is, I can say without hesitation or reservation, the greatest website ever made, at least in my own flawed estimation.
I have no idea who made it. Or why. Or what substances they were under the influence of. It doesn’t matter, I enjoy the mystery. If I had to pick a back story, it would be: Thomas Pynchon got bored and spent two years working on an anonymous website whenever he was drunk.
Although the site is available at the Internet Wayback Machine, I have been anxious, whenever I think about it, that the site will eventually get lost or purged from there. Thanks to this very, very helpful tool, I was able to easily download the entire site and make it available as the Official Unofficial Mirror on my site. Enjoy your symptom!
{}oe|e|ep[]
When Should I Use @State, @Binding, @ObservedObject, @EnvironmentObject, or @Environment?
SwiftUI introduced a laundry list of new property wrappers that your code can use to bridge the gap between program state and your views:
@State
@Binding
@ObservedObject
@EnvironmentObject
@Environment
That’s only a partial list. There are other property wrappers for Core Data fetch requests and gesture recognizers. But this post isn’t about these other wrappers. Unlike the wrappers above, the use cases for @FetchRequest
and @GestureState
are unambiguous, whereas it can be very confusing how to decide when to use a @State
versus @Binding
, or @ObservedObject
versus @EnvironmentObject
, etc.
This post is an attempt to define in simple, repeatable terms when each wrapper is an appropriate choice. There’s a risk I’m being overly prescriptive here, but I’ve gotten some decent mileage from these rules already. Besides, being overly prescriptive is a time-honored programmer blog tradition, so I’m in good albeit occasionally obnoxious company.
All of the code samples that follow are available in this GitHub repo. Note for posterity: this post was written using Swift 5 and iOS 13.
Cheat Sheet
-
Use
@State
when your view needs to mutate one of its own properties. -
Use
@Binding
when your view needs to mutate a property owned by an ancestor view, or owned by an observable object that an ancestor has a reference to. -
Use
@ObservedObject
when your view is dependent on an observable object that it can create itself, or that can be passed into that view’s initializer. -
Use
@EnvironmentObject
when it would be too cumbersome to pass an observable object through all the initializers of all your view’s ancestors. -
Use
@Environment
when your view is dependent on a type that cannot conform to ObservableObject. -
Also use
@Environment
when your views are dependent upon more than one instance of the same type, as long as that type does not need to be used as an observable object. -
If your view needs more than one instance of the same observable object class, you are out of luck. You cannot use
@EnvironmentObject
nor@Environment
to resolve this issue. (There is a hacky workaround, though, at the bottom of this post).
Your view needs a @State
property if…
…it needs read/write access to one of its own properties for private use.
A helpful metaphor is the isHighlighted
property on UIButton. Other objects don’t need to know when a button is highlighted, nor do they need write access to that property. If you were implementing a from-scratch button in SwiftUI, your isHighlighted
property would be a good candidate for an @State wrapper.
struct CustomButton<Label>: View where Label : View {
let action: () -> Void
let label: () -> Label
/// it needs read/write access to one of
/// its own properties for private use
@State private var isHighlighted = false
}
…it needs to provide read/write access of one of its properties to a descendant view.
Your view does this by passing the projectedValue
of the @State-wrapped property, which is a Binding to that value1. A good example of this is SwiftUI.Alert. Your view is responsible for showing the alert by changing the value of some @State, like an isPresentingAlert
boolean property. But your view can’t dismiss that alert itself, nor does the alert have any knowledge of the view that presented it. This dilemma is resolved by a Binding. Your view passes the Alert a Binding to its isPresentingAlert
property by using the compiler-generated property self.$isPresentingAlert
, which is syntax sugar for the @State wrapper’s projected value. The .alert(isPresented:content:)
modifier takes in that binding, which is later used by the alert to set isPresentingAlert
back to false, in effect allowing the alert to dismiss itself.
struct MyView: View {
/// it needs to provide read/write access of
/// one of its properties to a descendant view
@State var isPresentingAlert = false
var body: some View {
Button(action: {
self.isPresentingAlert = true
}, label: {
Text("Present an Alert")
})
.alert(isPresented: $isPresentingAlert) {
Alert(title: Text("Alert!"))
}
}
}
Your view needs a @Binding
property if…
…it needs read/write access to a State
-wrapped property of an ancestor view
This is the reverse perspective of the alert problem described above. If your view is like an Alert, where it’s dependent upon a value owned by an ancestor and, crucially, needs mutable access to that value, then your view needs a @Binding to that value.
struct MyView: View {
@State var isPresentingAlert = false
var body: some View {
Button(action: {
self.isPresentingAlert = true
}, label: {
Text("Present an Alert")
})
.customAlert(isPresented: $isPresentingAlert) {
CustomAlertView(title: Text("Alert!"))
}
}
}
struct CustomAlertView: View {
let title: Text
/// it needs read/write access to a State-
/// wrapped property of an ancestor view
@Binding var isBeingPresented: Bool
}
…it needs read/write access to a property of an object conforming to ObservableObject
but the reference to that object is owned by an ancestor.
Boy that’s a mouthful. In this situation, there are three things:
- an observable object
- some ancestor view that has an @-Something wrapper referencing that object
- your view, which is a descendant of #2.
Your view needs to have read/write access to some member of that observable object, but your view does not (and should not) have access to that observable object. Your view will then define a @Binding property for that value, which the ancestor view will provide when your view is initialized. A good example of this is any reusable input view, like a picker or a text field. A text field needs to be able to have read/write access to some String property on another object, but the text field should not have a tight coupling to that particular object. Instead, the text field’s @Binding property will provide read/write access to the String property without exposing that property directly to the text field.
struct MyView: View {
@ObservedObject var person = Person()
var body: some View {
NamePicker(name: $person.name)
}
}
struct NamePicker: View {
/// it needs read/write access to a property
/// of an observable object owned by an ancestor
@Binding var name: String
var body: some View {
CustomButton(action: {
self.name = names.randomElement()!
}, label: {
Text(self.name)
})
}
}
Your view needs an @ObservedObject
property if…
…it is dependent on an observable object that it can instantiate itself.
Imagine you have a view that displays a list of items pulled down from a web service. SwiftUI views are transient, discardable value types. They’re good for displaying content, but not appropriate for doing the work of making web service requests. Besides, you shouldn’t be mixing user interface code with other tasks as that would violate the Single Responsibility Principle. Instead your view might offload those responsibilities to an object that can coordinate the tasks needed to make a request, parse the response, and map the response to user interface model values. Your view would own a reference to that object by way of an @ObservedObject wrapper.
struct MyView: View {
/// it is dependent on an object that it
/// can instantiate itself
@ObservedObject var veggieFetcher = VegetableFetcher()
var body: some View {
List(veggieFetcher.veggies) {
Text($0.name)
}.onAppear {
self.veggieFetcher.fetch()
}
}
}
…it is dependent on a reference type object that can easily be passed to that view’s initializer.
This scenario is nearly identical to the previous scenario, except that some other object besides your view is responsible for initializing and configuring the observable object. This might be the case if some UIKit code is responsible for presenting your SwiftUI view, especially if the observable object can’t be constructed without references to other dependencies that your SwiftUI view cannot (or should not) have access to.
struct MyView: View {
/// it is dependent on an object that can
/// easily be passed to its initializer
@ObservedObject var dessertFetcher: DessertFetcher
var body: some View {
List(dessertFetcher.desserts) {
Text($0.name)
}.onAppear {
self.dessertFetcher.fetch()
}
}
}
extension UIViewController {
func observedObjectExampleTwo() -> UIViewController {
let fetcher = DessertFetcher(preferences: .init(toleratesMint: false))
let view = ObservedObjectExampleTwo(dessertFetcher: fetcher)
let host = UIHostingController(rootView: view)
return host
}
}
Your view needs an @EnvironmentObject
property if…
…it would be too cumbersome to pass that observed object through all the initializers of all your view’s ancestors.
Let’s return to the second example from the @ObservedObject section above, where an observable object is needed to carry out some tasks on behalf of your view, but your view is unable to initialize that object by itself. But let’s now imagine that your view is not a root view, but a descendant view that is deeply nested within many ancestor views. If none of the ancestors need the observed object, it would be painfully awkward to require every view in that chain of views to include the observed object in their initializer arguments, just so the one descendant view has access to it. Instead, you can provide that value indirectly by tucking it into the SwiftUI environment around your view. Your view can access that environment instance via the @EnvironmentObject wrapper. Note that once the @EnvironmentObject’s value is resolved, this use case is functionally identical to using an object wrapped in @ObservedObject.
struct SomeChildView: View {
/// it would be too cumbersome to pass that
/// observed object through all the initializers
/// of all your view's ancestors
@EnvironmentObject var veggieFetcher: VegetableFetcher
var body: some View {
List(veggieFetcher.veggies) {
Text($0.name)
}.onAppear {
self.veggieFetcher.fetch()
}
}
}
struct SomeParentView: View {
var body: some View {
SomeChildView()
}
}
struct SomeGrandparentView: View {
var body: some View {
SomeParentView()
}
}
Your view needs an @Environment
property if…
…it is dependent on a type that cannot conform to ObservableObject.
Sometimes your view will have a dependency on something that cannot conform to ObservableObject, but you wish it could because it’s too cumbersome to pass it as a initializer argument. There are a number of reasons why a dependency might not be able to conform to ObservableObject:
- The dependency is a value type (struct, enum, etc.)
- The dependency is exposed to your view only as a protocol, not as a concrete type
- The dependency is a closure
In cases like these, your view would instead use the @Environment wrapper to obtain the required dependency. This requires some boilerplate to accomplish correctly.
struct EnvironmentExampleOne: View {
/// it is dependent on a type that cannot
/// conform to ObservableObject
@Environment(\.theme) var theme: Theme
var body: some View {
Text("Me and my dad make models of clipper ships.")
.foregroundColor(theme.foregroundColor)
.background(theme.backgroundColor)
}
}
// MARK: - Dependencies
protocol Theme {
var foregroundColor: Color { get }
var backgroundColor: Color { get }
}
struct PinkTheme: Theme {
var foregroundColor: Color { .white }
var backgroundColor: Color { .pink }
}
// MARK: - Environment Boilerplate
struct ThemeKey: EnvironmentKey {
static var defaultValue: Theme {
return PinkTheme()
}
}
extension EnvironmentValues {
var theme: Theme {
get { return self[ThemeKey.self] }
set { self[ThemeKey.self] = newValue }
}
}
…your views are dependent upon more than one instance of the same type, as long as that type does not need to be used as an observable object.
Since @EnvironmentObject only supports one instance per type, that idea is a non-starter. Instead if you need to register multiple instances of a given type using per-instance key paths, then you will need to use @Environment so that your views’ properties can specify their desired keypath.
struct EnvironmentExampleTwo: View {
@Environment(\.positiveTheme) var positiveTheme: Theme
@Environment(\.negativeTheme) var negativeTheme: Theme
var body: some View {
VStack {
Text("Positive")
.foregroundColor(positiveTheme.foregroundColor)
.background(positiveTheme.backgroundColor)
Text("Negative")
.foregroundColor(negativeTheme.foregroundColor)
.background(negativeTheme.backgroundColor)
}
}
}
// MARK: - Dependencies
struct PositiveTheme: Theme {
var foregroundColor: Color { .white }
var backgroundColor: Color { .green }
}
struct NegativeTheme: Theme {
var foregroundColor: Color { .white }
var backgroundColor: Color { .red }
}
// MARK: - Environment Boilerplate
struct PositiveThemeKey: EnvironmentKey {
static var defaultValue: Theme {
return PositiveTheme()
}
}
struct NegativeThemeKey: EnvironmentKey {
static var defaultValue: Theme {
return NegativeTheme()
}
}
extension EnvironmentValues {
var positiveTheme: Theme {
get { return self[PositiveThemeKey.self] }
set { self[PositiveThemeKey.self] = newValue }
}
var negativeTheme: Theme {
get { return self[NegativeThemeKey.self] }
set { self[NegativeThemeKey.self] = newValue }
}
}
Workaround for Multiple Instances of an EnvironmentObject
While it is technically possible to register an observable object using the .environment()
modifier, changes to that object’s @Published
properties will not trigger an invalidation or update of your view. Only @EnvironmentObject
and @ObservedObject
provide that. Unless something changes in the upcoming iOS 14 APIs, there is only one recourse I have found: a hacky but effective workaround using a custom property wrapper.
-
You must register each instance using the
.environment()
modifier, not the.environmentObject()
modifier. -
You need a custom property wrapper conforming to
DynamicProperty
that owns a private@ObservedObject
property whose value is retrieved during initialization by pulling it from a single-shot instantiation of anEnvironment<T>
struct (used as an instance, not as a property wrapper).
With this set up in place, your view can observe multiple objects of the same class:
struct MyView: View {
@DistinctEnvironmentObject(\.posts) var postsService: Microservice
@DistinctEnvironmentObject(\.users) var usersService: Microservice
@DistinctEnvironmentObject(\.channels) var channelsService: Microservice
var body: some View {
Form {
Section(header: Text("Posts")) {
List(postsService.content, id: \.self) {
Text($0)
}
}
Section(header: Text("Users")) {
List(usersService.content, id: \.self) {
Text($0)
}
}
Section(header: Text("Channels")) {
List(channelsService.content, id: \.self) {
Text($0)
}
}
}.onAppear(perform: fetchContent)
}
}
// MARK: - Property Wrapper To Make This All Work
@propertyWrapper
struct DistinctEnvironmentObject<Wrapped>: DynamicProperty where Wrapped : ObservableObject {
var wrappedValue: Wrapped { _wrapped }
@ObservedObject private var _wrapped: Wrapped
init(_ keypath: KeyPath<EnvironmentValues, Wrapped>) {
_wrapped = Environment<Wrapped>(keypath).wrappedValue
}
}
// MARK: - Wherever You Create Your View Hierarchy
MyView()
.environment(\.posts, Microservice.posts)
.environment(\.users, Microservice.users)
.environment(\.channels, Microservice.channels)
// each of these has a dedicated EnvironmentKey
Sample Code
All of the code above is available in an executable form here.
-
Every
@propertyWrapper
-conforming type has the option of providing aprojectedValue
property. It is up to each implementation to decide the type of the value. In the case of theState<T>
struct, the projected value is aBinding<T>
. It behoves you, any time you’re using a new property wrapper, to jump to its generated interface to discover in detail what it’s projected value is. ↩
Addicted to Jesus
I was pushing forty before I was able to articulate this:
Religion and philosophy are bad for me.
Not “are bad”, but “are bad for me”, specifically me, in the way the same liquor that drags one person down passes quietly over the next. I don’t drink. I’ve another poison. I’m fatally attracted to unanswerable questions. I’m always slouching toward a downward spiral of obsessive philosophical and religious thought, trying to resolve irresolvable thought problems, torturing myself over that failure by trying ever harder, until I develop old-fashioned clinical depression. From my teenage years onward, throughout all of my twenties, I was transfixed by big, vexing questions. The usual suspects: the problem of evil, the existence of god, blah blah blahd nauseam. But not in a casual way. Never in a pot-fueled like, what if we’re all in a simulation, man reverie. This pain cut deep, pervaded every moment of my life. I tore apart books, filled journals with angsty blather, changed jobs and majors like clothes, looking for answers to questions that have none. I was addicted.
"The Upper Zoom" by @MythAddict.
That notion — those words even: religion and philosophy are bad for me — is not a thing I’ve ever heard before. It’s not a part of any tradition I know of. There are no shortage of true unbelievers who’ll blame the human condition on religion1. This ain’t that. I’m saying that they’re bad for me, and it doesn’t matter whether they’re a cause of or the cure for society’s ills. I can’t safely be around them. I was raised in an evangelical subculture that was profoundly committed to a certain view of right living, often harmful, sometimes heart-breakingly beautiful, spotlit by a few passages of truly moving literature. One thing evangelicals and atheists and whateverists of all stripes have in common is the assumption that the nuts n’ bolts of religio-philosophical thought are value-neutral. Abstracted from any conclusions drawn, the practical mechanics of the thing aren’t considered a threat. “Assumption” is too strong a word. It’s simply not under consideration.
When I finally figured out that I am not like other people, that sustained religious and philosophical thinking poses a perpetual threat to my mental health, it was liberating. In practical terms, I was able to spot the warning signs and put myself out of harm’s way. Put down that book, jackass, go do something grounded.
I can’t say for sure, but I attribute at least some of my predicament to my evangelical roots. That contradictory tension of “you must believe this but you had better not actually live this,” the impossible sacrifice we were all expected to recite but never required to follow, — so many contradictions — it’s inevitable that it’s going to churn out some kids like me, kids who go into adulthood feeling like they have no home, can’t go back to the bullshittery of the past, but are still addicted to Jesus.
-
Faith traditions are powerful motivators both for good and for evil. Don’t @-me with your religion-is-the-cause-of-evil bullshit. ↩