Swift Refresher Course - Day 2

Same structure as Day 1:

  1. Concept Overview
  2. Code Examples
  3. Hands-on Exercises
  4. Interview Angle
  5. Resources Review

Concept Overview

Property Wrappers in Swift (Introduced in Swift 5.1)

Ooo, this'll be fun. Here's what the LLM says about Property Wrappers.

What they are: A Swift feature that lets you define a reusable pattern for how a property is stored, observed, or transformed. They’re syntactic sugar around getter/setter logic.

Key idea: @Wrapper var value: T compiles down to a private _value plus projected access $value (if the wrapper defines one).

In SwiftUI: Many wrappers provide state management in the reactive rendering system.

Core ones in SwiftUI / Combine:

• @State – Local, view-owned state. Value-type, lives for the life of the view struct.

• @Published – Marks a property in an ObservableObject as emitting Combine publisher events when it changes.

• @AppStorage – Persists a value in UserDefaults while keeping it reactive in SwiftUI views.

• @Environment / @EnvironmentObject – Injects dependencies into the SwiftUI hierarchy.

Whew, that's a lot to verify! Let's dive in.

Things I'd like to verify about property wrappers

  • Are property wrappers actually just "syntactic sugar around getter/setter logic"?
  • Explore the "Key Idea" above. Is that correct?
  • What does it mean that "wrappers provide state management in the reactive rendering system"?
  • Is that list of "Core wrappers in SwiftUI / Combine" reasonable? Are there any others I should think about?
  • (bonus) Swift 6.2 was just released. Given that the knowledge cutoff of the LLM probably doesn't know about it, is there anything from newer Swift versions that might contradict the above?

Just Syntactic Sugar?

Seems like the best place to start here would be the source: The Swift Programming Language Entry on Property Wrappers. And I think the LLM said something correct, and I twisted it in a wrong way in my head.

I read the LLM's assertion about them being syntactic sugar, and imputed a casualness that doesn't really exist, saying "Just syntactic sugar". It's not the same as the way var++ is syntactic sugar for var = var + 1.

Property Wrappers let you apply some standard handling to the storage and retrieval of a property. That can be quite complicated (which I already knew). So I thought the LLM was wrong in saying 'syntactic sugar', as I thought it implied 'simple'. But it doesn't.

The Key Idea

Ahh, now I think I've got the LLM here. John Sundell has a lovely little explainer on property wrappers, and this phrase caught my eye:

Besides that, the only real requirement is that each property wrapper type should contain a stored property called wrappedValue, which tells Swift which underlying value that’s being wrapped.

The Swift Programming Language book page is also pretty explicit about that variable name:

To define a property wrapper, you make a structure, enumeration, or class that defines a wrappedValue property.

And finally, the actual Swift compiler diagnostics page is extremely clear:

First, all property wrapper types must have a property named wrappedValue.

The LLM's little boiling down makes absolutely no mention of that. Plus, it's using the underscore-prefaced _value, which seems more like an Objective-C convention to me.

(Fun Fact: In Dart, private variables are prefaced by an underscore. The compiler enforces it. This is not my favourite fun fact.)

I see what the LLM was trying to do, and it's sorta kinda correct if I squint, but it's too misleading. I'm glad I dug a little deeper here.

Wrappers Provide State Management SwiftUI - And the list of Core wrappers

I already knew that property wrappers are key tools in SwiftUI. The ones the LLM listed look right to me, so really I'm just going to do some quick googling here and see if this is all on the up-and-up.

Aaaand Thank you Paul Hudson, who wrote up precisely the list I need this year! This is a phenomenal article. All killer, no filler, as they say. Within, Paul lists 17 separate property wrappers used in SwiftUI, and explains what they do.

So here, I'm deciding that my task is to determine whether or not I agree with the LLM about whether the 5 it chose are the RIGHT ones, and whether others (or more) should have been added.

Here are the property wrappers the LLM suggested. I'll put a check next to the ones where the description agrees with Paul's:

  • @State
  • ✅❓ @Published (Generally agress, but the LLM says it's emitting Combine publisher events? That seems sus.)
  • @AppStorage
  • 😬 @Environment/@EnvironmentObject I think Paul's description here is way more helpful: "lets us read data from the system, such as color scheme, accessibility options, and trait collections, but you can add your own keys here if you want. This does not own its data."

So, mixed success for the LLM here. Some things I think could have been added to the list:

  • @StateObject, for storing instances of reference data (classes rather than structs). The LLM correctly marked @State as locally owned (by the View it lives in), but it would have been nice to see the other side as well.
  • @ObservedObject/ObservableObject, which is maybe pushing into the weeds a bit for an overview, but once you're using @StateObject, the object itself needs conform to the ObservableObject protocol. @ObservedObject is a wrapper you can use to mark an object that your view doesn't own, but can publish data to the view.
  • @Binding is one I've seen pop up a bunch, and is used similarly to @ObservedObject, but differs in that it's for value types, NOT reference types.

There are quite a few others, and I'll definitely be coming back to Paul's list (and associated articles) often.

(bonus) Anything new from Swift 6.2?

I've actually decided not to worry about this. The Paul Hudson article I linked above was most recently updated for Xcode 16.4, which is QUITE recent. Xcode 26.0 RC was only released a couple of days ago, so I'm not too worried that there are any breaking changes.

Functional Patterns

Here's what the LLM says:

Swift borrowed heavily from FP:

- map: Transform each element into another (same cardinality).

- flatMap: Flatten nested optionals/collections while mapping (e.g. [Int?] → [Int]).

- reduce: Fold a sequence into a single value using an accumulator.

Works on arrays, optionals, Combine publishers, and even SwiftUI views.

Key takeaway for interviews: These encourage value transformation over mutation.

I don't need to do much research here; I've used functional patterns like these in various places, in Typescript, Dart, Swift, et cetera.

Something about the flatMap descriptor seems off.. I remember when Swift was released, flatMap didn't do what I thought it did. Specifically, I originally thought it was just like map, but that it would ignore nil Optionals.

But THAT is what compactMap does, which isn't listed by the LLM (probably an error).

So the description above seems more appropriate to compactMap. Instead, for flatMap, it should be:

flatMap - (e.g. [Int? [Int?]] → [Int])

Additionally, saying 'it works on arrays' seems simplistic. It doesn't just work on Arrays, it works on any type that conforms to The Sequence Protocol.

👀 "The Sequence Protocol" sounds like it would be a good name for some kind of near-future spy thriller.

Code Examples

Property Wrappers In SwiftUI

So the LLM used two property wrappers here, @State and @AppStorage. The @State wrapper means that the count variable is local to this CounterView (which is also mandated by the private access modifier).

As I saw before, @AppStorage is just used for saving & retrieving data from UserDefaults. So that's intresting. Does this mean that every time highScore changes, that change will be written to UserDefaults?

🪏 Digging a little deeper: Once again, Paul Hudson to the rescue. Yes, it does seem that it'll write to UserDefaults every time it changes. I'm curious what kind of performance penalty that might have.

But this is pretty straightforward.

struct CounterView: View {
    @State private var count = 0
    @AppStorage("highScore") private var highScore = 0
    
    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("Increment") {
                count += 1
                if count > highScore { highScore = count }
            }
            Text("High Score: \(highScore)")
        }
    }
}

ObservableObject + Published

Ok, so we know that ObservableObject is a protocol for letting SwiftUI see reference types (eg. classes).

Here, we have the UserSettings class conform to the protocol, and wraps the username property with the @Published wrapper, which works hand-in-hand with the ObservableObject protocol.

🤔 I'm curious what happens when you implement ObservableObject but don't add any @Published properties. I suspect it's a bit like putting a tuxedo on a donkey. All dressed up, with nowhere to go.

We also have a @StateObject in the View here. I remember that this is how you actually access an ObservableObject-conforming instance in SwiftUI; You wrap the instance in that property wrapper, and it will then refresh your View when the object changes. OR, more to the point; when the @Published-wrapped properties of the object change.

🤔 If you changed the ObservableObject itself (eg. settings = UserSettings(), reinstantiate it), would that result in a publishing of the username property? I should check that. Later.

class UserSettings: ObservableObject {
    @Published var username: String = ""
}

struct ProfileView: View {
    @StateObject private var settings = UserSettings()
    
    var body: some View {
        VStack {
            TextField("Name", text: $settings.username)
            Text("Hello, \(settings.username)")
        }
    }
}

Functional Patterns

HAH! Look at the middle section; the comment says flatMap, but the actual code uses compactMap!! I KNEW IT! 😂

This is pretty straightforward otherwise. I'm not going to spend any extra time here, I get it.

let scores = [5, 10, 15, 20]

// map: double each score
let doubled = scores.map { $0 * 2 }   // [10, 20, 30, 40]

// flatMap: remove nils and flatten
let optionals: [Int?] = [1, nil, 3]
let flattened = optionals.compactMap { $0 } // [1, 3]

// reduce: sum them
let total = scores.reduce(0, +) // 50

Hands-On Exercises

Sweet, I think these are kinda my favourite part.

📝 Note: I turn off the Predictive Code features in Xcode when I work on these. I feel having the IDE fill in what I should do really defeats the purpose of practice.

Counter with Persistence

Build a SwiftUI counter using @State for the count and @AppStorage for high score. Try quitting/restarting the app to see persistence.

Ok this is kind of annoying, because this is the exact thing that it gave me in the code examples. I'll try & rebuild it without actually looking at the previous code.

import Cocoa
import SwiftUI
import PlaygroundSupport

struct CounterWithPersistence: View {
    @State var count = 0
    @AppStorage("highscore") var highScore = 0
    
    var body: some View {
        VStack {
            HStack {
                Text("Count")
                Text(String(count))
            }
            HStack {
                Text("High Score")
                Text(String(highScore))
            }
            Button("Increment Count") {
                count += 1
                if(count > highScore) {
                    highScore = count
                }
            }
        }
        
    }
}

PlaygroundPage.current.setLiveView(CounterWithPersistence())

That wasn't too bad! Seems to work the way I want.

ObservableObject Form

Create a form with TextField and Toggle bound to @Published properties in a model. Display a live preview of changes below.

🤔 I'm not sure if there's some kind of 'form control' it wants me to use, but I'm not going to worry about that for now. Just build something super simple.

class FormViewModel: ObservableObject {
    @Published var text: String = ""
    @Published var isOn: Bool = false
}

struct ObservableObjectForm: View {
    @ObservedObject var model: FormViewModel = FormViewModel()
    var body: some View {
        VStack {
            HStack {
                TextField("Enter text", text: $model.text)
                Toggle(isOn: $model.isOn) {
                    Text("flip it")
                }
            }
            HStack {
                Text(model.text)
                Text(model.isOn ? "✅" : "❌")
            }
            
        }
    }
}

Does the job!

🤔 Looking at this again though, it's interesting. I used @ObservedObject instead of @StateObject. I don't think I quite understand the difference between those two wrappers yet.

Functional Refactor

Given [“Swift”, “Objective-C”, “Flutter”], use map, flatMap, and reduce to:

- Count total characters across all strings.

- Flatten a nested array like [["S","w"], ["i","f","t"]] into one string "Swift".

Ok, this is pretty straightforward, I think!

📝 Note: The LLM outputs unicode curly quotes above, which Xcode does NOT like.

let strings = ["Swift", "Objective-C", "Flutter"]
let nestedArray = [["S", "w"], ["i", "f", "t"]]

let totalCharacterCount = strings.map { $0.count }
    .reduce(0, +)
let swift = nestedArray.flatMap { $0 }
    .reduce("", +)

// totalCharacterCount is 23
// swift is "Swift"

I'll also note here that the LLM didn't give me any nils in the arrays, which could have been managed with compactMap.

Interview Angle

Q1: Explain the difference between @State and @Published.

A: @State is view-local, value-type storage used for simple properties inside a view struct. @Published is for properties on reference-type ObservableObjects that emit change events to subscribers. @State drives only the owning view; @Published can notify any view or subscriber observing it.

Verification: Yeah, this is basically correct. I had remembered most of the answer above, except for remembering to mention that @Published properties can be viewed in any view or subscriber. I DID remember that @State is local to the View that contains the property.

Q2: What does map vs flatMap do in Swift?

A: map transforms each element and preserves structure. flatMap both maps and flattens nested optionals/collections — removing one level of wrapping.

Verification: Once again, I think the LLM is doing something wrong with flatMap here. On the developer docs page for FlatMap, it doesn't say anything about Optionals. However, the compactMap page does.

I'm not 100% sure, but I think this Q/A is wrong? Maybe?

Q3: How does SwiftUI know to redraw when a property changes?

A: Through property wrappers (@State, @Published, etc.) that integrate with SwiftUI’s diffing engine. They trigger a View body recomputation when their value changes, enabling declarative UI updates.

Verification: As soon as I saw this question, I knew I was going to be unsatisfied by the answer. While it is technically correct, I think I just knew that SwiftUI does diffs on the View tree, I didn't see that offered up anywhere in this day's 'learning'.

I guess I just want to know more about exactly how this works. Out-of-scope for today's thing, but I'll mark it down for follow-up.

Resources for Deeper Dive