One. Course Details
This is the fifteenth lecture of Stanford University's CS193p iOS Application Development course for Spring 2025, taught by Paul Hegarty. The lecture begins with minor SwiftData improvements to the CodeBreaker application, then transitions to a comprehensive conceptual introduction to multithreading and Swift's modern concurrency model.
The lecture covers core concurrency concepts including actors, isolation, suspension points, async/await, Tasks, and the MainActor. It concludes with a line-by-line analysis of the words.swift network download code from earlier assignments, demonstrating how these concurrency concepts apply in practice. The lecture ends with advice for students starting their final projects.
Two. Key Learning Takeaways
SwiftData predicate limitations: #Predicate expressions are converted to SQL and can only access stored properties in the database. Computed properties are not visible to the database and cannot be used in predicates.
Database save notifications: Use ModelContext.willSave and ModelContext.didSave notifications to execute code immediately before or after the database is saved.
Multithreading core principle: Never block the UI thread. Even a 0.5-second freeze will result in a poor user experience and should be avoided at all costs.
Swift concurrency three pillars:-
Actors and isolation: Synchronize access to shared data to prevent data races
-
Waiting and suspension: Allow threads to pause execution without blocking
-
Synchronous vs asynchronous contexts: Define where async operations can be performed Actor guarantees: Only one function or property access can execute on an actor at a time, and code runs to completion or stops only at explicit suspension points.
asyncandawait:asyncmarks functions that may suspend;awaitmarks the actual suspension point when calling an async function or accessing another actor.Taskclosures: Create an asynchronous execution context whereawaitcan be used. Tasks inherit the actor isolation of their creation context.@MainActor: A global actor that runs all UI code. Marking classes, functions, or properties with@MainActorensures they execute only on the main thread.Sendableprotocol: Marks types that can safely cross actor boundaries. Value types (structs, enums) and actors are inherently Sendable; most classes are not. Strict concurrency checking: Enable complete concurrency checking in Xcode to catch potential data races at compile time.
Three. Course Gold Quotes
"Don't block my UI. That's what multithreading is all about here. It is never okay in a mobile app UI to have your UI freeze even for half a second." "Actors are the fundamental way that we make sure things don't stomp on each other." "Await is the keyword that you can always look for in an actor to see whether it can suspend there." "Your entire UI runs on the MainActor. This happens naturally in SwiftUI, so you don't really need to think about this." "Programming is like doing crossword puzzles. You can't get 26-across, you go to sleep, you wake up the next day and you know what it is." "Give your subconscious a chance to work on your final project by starting it early and working on it a little bit day by day." "Go do stuff I didn't do. Maps, Swift Charts, things like that. Go figure it out. That's how you learn."Four. Layered Learning Notes
Module 1: SwiftData Advanced Techniques
Predicate Limitations and Workarounds
-
Problem: Using a computed property like
game.isOverin a#Predicatewill crash because computed properties do not exist in the database schema. -
Solution 1: Convert the computed property to a stored property and update it manually whenever the game state changes. This keeps game logic in the model where it belongs.
-
Solution 2: Rewrite the predicate to use only stored properties, including cross-table relationships:
swift#Predicate<CodeBreaker> { game in game._attempts.contains { $0.pegs == game.masterCode.pegs } } -
Important note: Always use the actual stored property name (including any leading underscores) in predicates, not the computed wrapper name.
Listening for Database Save Events
-
Use
NotificationCenterto observeModelContext.willSavenotifications:
swiftlet publisher = NotificationCenter.default.publisher( for: ModelContext.willSave, object: modelContext ) -
Use the
.onReceive(publisher)view modifier to execute code before each save:
swift.onReceive(publisher) { _ in game.updateElapsedTime() } -
This ensures transient state like elapsed time is properly persisted before autosave.
Module 2: Multithreading Fundamentals
Core Problem
-
Long-running operations (network requests, heavy computation) will block the UI thread if executed synchronously, causing freezes and poor user experience.
-
Multithreading allows multiple lines of code to execute simultaneously, keeping the UI responsive while background work completes.
Data Races
-
The primary danger of multithreading: two threads accessing and modifying the same shared data at the same time, leading to corrupted state or crashes.
-
Swift's concurrency model is designed to eliminate data races entirely through language-level guarantees.
Module 3: Swift Concurrency Model Core
Actors
-
Actors are reference types (similar to classes) that synchronize all access to their internal state.
-
Guarantees:
-
Only one function or property access can execute on an actor at any time
-
Code runs to completion or stops only at explicit suspension points
-
-
This serialization of access completely eliminates data races for data contained within an actor.
Isolation
-
Functions and properties on an actor are said to be "isolated" to that actor.
-
Code outside the actor must use
awaitto call isolated functions or access isolated properties. -
The
nonisolatedkeyword can be used to opt out of isolation for specific functions that do not access shared state.
Suspension Points
-
A suspension point is where an actor pauses execution of one function to allow another function to run.
-
Suspension points are explicitly marked with the
awaitkeyword. -
Critical rule: All data structures must be in a consistent state before any
awaitcall, as the world may have changed when execution resumes.
async and await
-
async: Marks a function that may contain suspension points. Advertises to callers that they must useawait. -
await: Marks the actual point where suspension may occur. Indicates that the code will wait for an async operation to complete. -
awaitcan only be used within an asynchronous context.
Module 4: Asynchronous Contexts and Tasks
Creating Asynchronous Contexts
There are two ways to create an environment whereawait can be used:
-
Mark a function with
async -
Use a
Taskclosure:
swiftTask { // Async code with await can go here }
Task Behavior
-
Taskreturns immediately and executes its closure asynchronously at some point in the future. -
Tasks inherit the actor isolation of their creation context.
-
The returned
Taskobject can be used to cancel the task or retrieve its result.
Built-in Async View Modifiers
-
.task(): Executes an async closure when a view appears and automatically cancels it when the view disappears. Perfect for loading view data. -
.refreshable(): Adds pull-to-refresh functionality to lists, executing an async closure when the user pulls down.
Module 5: MainActor and UI Concurrency
-
The
MainActoris a global actor that runs all UI code. All SwiftUI views and their properties are automatically isolated to the MainActor. -
Usage:
-
Mark entire classes with
@MainActorto ensure all their methods and properties run on the main thread -
Mark individual functions or properties with
@MainActor -
Force a Task closure to run on the MainActor:
swiftTask { @MainActor in // All code here runs on the main thread }
-
-
This is the simplest way to add thread safety to small amounts of multithreaded code, especially when interacting with the UI.
Module 6: Sendable Protocol
-
Sendableis a marker protocol with no requirements. It indicates that a type can safely cross actor boundaries. -
Sendable types:
-
All value types (structs, enums) that contain only other Sendable types
-
All actors
-
Classes with no mutable state or only thread-safe mutable state
-
-
Non-Sendable types: Most classes, as they are inherently mutable and shared.
-
Closures can be marked
@Sendableto indicate they can safely cross actor boundaries.
Module 7: Practical Concurrency and Best Practices
Analyzing words.swift
-
The network download code uses a
Taskclosure to create an asynchronous context. -
for await word in url.linesdemonstrates async sequences, which allow processing streaming data incrementally. -
The original code is not thread-safe because it uses shared mutable state without actor isolation.
-
Fix: Mark the entire
Wordsclass with@MainActorto ensure all accesses are synchronized on the main thread.
Strict Concurrency Checking
-
Enable "Complete" strict concurrency checking in Xcode build settings to catch potential data races at compile time.
-
This is an invaluable tool for writing safe concurrent code, even though it may produce warnings for older Apple frameworks that have not yet been updated for Swift 6 concurrency.


