Note Wisdom
These annotated notes for Stanford CS193p Lesson 15 walk through SwiftUI demo fixes and Swift modern concurrency concepts including multithreading, actors, async‑await, and Sendable plus project tips.
Institution: Stanford
Original Course: Stanford CS193p: iOS Development with SwiftUI | 2025 | L15: Multithreading
Instructor Bio: This lecture is delivered by Paul Hegarty, Lecturer in Computer Science at Stanford University and the principal instructor of CS 193P since 2010. Paul Hegarty is a veteran software engineer and educator with deep roots in the Apple developer ecosystem. Earlier in his career he worked at NeXT Computer, where he contributed to the Objective-C language and the foundational tools that would eventually become Apple's modern development platform. He has taught iOS application development at Stanford for over fifteen years, guiding thousands of students through the transition from earlier UIKit frameworks to the modern SwiftUI declarative paradigm. He is widely recognized for his methodical teaching style, his emphasis on clean architectural patterns such as MVVM, and his ability to explain complex systems concepts through hands-on, live-coding demonstrations.
Course Description: This lecture covers multithreading and concurrency in Swift, essential for building responsive applications that perform work without freezing the user interface. It explains the @MainActor for UI updates, background dispatch queues for offloading heavy work, and Swift's modern async/await pattern for structured concurrency. The lecture also covers actors for safe shared mutable state, the Sendable protocol for thread-safe types, and demonstrates how to integrate multithreaded operations into a SwiftData-backed application while avoiding data races and maintaining UI responsiveness.
isOver. The app crashed immediately. The lecturer explained why: predicates compile down to SQL queries that run inside the database layer. The database cannot evaluate Swift‑side computed properties. Only actual stored model fields persist inside the database and can be used for filtering queries.isOver from a computed property into a stored Boolean model field. This meant manually updating this Boolean everywhere game state changed: on game startup, when users submit guesses that solve the puzzle, and when restarting games. The downside is extra bookkeeping work. You now have to remember to set this value correctly across multiple code locations, and you risk state getting out‑of‑sync if you forget one spot. The upside is game completion logic stays encapsulated within the model instead of living inside a UI‑layer predicate.isOver as a computed property. Instead they wrote a complex predicate that traversed related database tables directly. It checked whether any stored guess attempt matched the target solution code. This crashed at first too, because they referenced a computed wrapper property instead of the actual underlying stored database variable with an underscore prefix. After switching to the backing storage variable, the predicate worked. It performed cross‑table joins entirely within the database.willSave notification, which fires right before Swift Data writes changes to storage. Using the onReceive view‑modifier they ran a method to refresh elapsed time every time a database save was about to happen. The update method reused existing timer logic: pause then immediately restart the timer, which internally persisted the latest elapsed value. This lived inside a dedicated view‑modifier file they created for elapsed‑time tracking logic.await.
All contents below are exclusive to the paid Word file, NOT available on this web page
async. If a function lacks the async keyword it cannot suspend, it must run all the way through without pausing.async function you must write await. This signals to both compiler and human readers that execution might suspend at this location. When suspended the actor can run other work while waiting for something slow like a network call. Once the awaited operation finishes execution resumes from right after the await statement.await inside an asynchronous context. Two ways to get an async context: mark your function with async, or wrap code inside a Task closure. Tasks start off background concurrent work from normal synchronous code. Several SwiftUI view modifiers like .task and .refreshable automatically create async contexts for their closure arguments. The .task view modifier is especially handy: it starts when the view appears and automatically cancels itself when the view disappears.Sendable marker protocol. Sendable has no required methods. It is a compile‑time guarantee that this value can safely pass across actor boundaries without creating data races.@Sendable to enforce they only capture Sendable values. If you pass non‑Sendable values across actor boundaries you risk concurrent mutation bugs.@MainActor is a built‑in global actor. Every SwiftUI view automatically runs on the main actor, so all your view body code, @State variables, and view‑modifier closures are main‑actor‑isolated. This keeps all UI work serialized and thread‑safe.@MainActor is a quick, practical tool for small‑scale concurrency work. You can apply @MainActor to individual variables, functions, or an entire class. Anything annotated will execute on the main actor’s serial queue.@MainActor in on its closure, or use MainActor.run(). The lecturer preferred the task‑based syntax.Task to create an async context so it can perform network operations. They demonstrated for‑await‑in for async sequences, which yields pieces of data incrementally, such as lines coming in over a network connection. Each iteration is a potential suspension point.@MainActor to the whole class makes all its properties and methods isolated to the main actor, fixing many concurrency warnings. But new issues pop up. For example @EnvironmentKey infrastructure does not support main‑actor‑isolated default values. The lecturer noted these are still rough edges in the Swift toolchain that Apple will likely resolve over time. Other minor warnings popped up for things like certain transition objects and key paths inside predicates, also known unresolved pain points.@MainActor‑isolated default values, some view transitions and key‑path expressions do not conform to Sendable yet. The lecturer said these are known gaps, and we cannot fully fix them ourselves without rewriting framework internals. There was no complete workaround shown for the environment‑key limitation. They mentioned they might post supplementary material for this after class, but we do not see that in the lecture recording..task view modifier automatic cancellation. The Words.swift example glossed over network failure handling entirely, focusing purely on the concurrency mechanics.willSave trick worked for their demo, but they did not cover edge‑cases like multiple overlapping saves or tearing down subscriptions when a view goes away. I wondered what happens if the notification publisher keeps firing after the view has been destroyed, though they quickly noted this pattern is not commonly used in modern SwiftUI.@MainActor will be sufficient for small‑scale background work like downloading files. If you need heavy‑duty parallel CPU‑bound work you would define your own custom actor, but he expected very few students would go that far.Skip hours of watching lectures. Get organized notes, exam prep materials and problem solutions all in one Word file.
Click to see everything included
All contents below are exclusive to the paid Word file, NOT available on this web page

