Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
241: Isolation Regions and Sendable Checking
Imagine you have a high-security physical file folder. Inside this folder is a sensitive document that isn't "digitized"—meaning it can't be emailed or copied (this is our non-Sendable type). Normally, if you wanted to give that document to a colleague in another building, you'd be told it's impossible because the document is too fragile to move. However, if you put the document inside a locked, tracked briefcase (the isolation region) and physically carry it to the other building, the security guards let you through. They aren't worried about the document itself being "portable"; they are worried about the process of moving it. As long as the document stays within that tracked briefcase during the transfer and remains under a single person's control at any given time, the safety rules are satisfied.
In Swift, an Isolation Region is essentially that tracked briefcase. For a long time, the compiler was binary: either a type was Sendable (safe to move across actor boundaries) or it wasn't. If it wasn't, the compiler would stop you dead in your tracks the moment you tried to pass it to an actor. But the Swift 6 compiler is smarter. It now tracks the "region" of isolation. If it can prove that a non-Sendable object is being moved from one isolated context to another without ever being accessed concurrently, it lets the code slide. It's not that the object suddenly became Sendable; it's that the compiler has proven the region of access is safe.
The "Wait, Why is This Allowed?" Moment
You've probably run into this if you've started experimenting with Swift 6 mode. You'll see a non-Sendable class being passed into an actor method, and you'll expect a warning. But sometimes, the warning doesn't appear. Here is why.
class UserSettings {
var theme: String = "Dark"
}
actor SettingsManager {
var currentSettings: UserSettings?
func applySettings(_ settings: UserSettings) {
// The compiler sees 'settings' entering the isolation region of SettingsManager.
// Since it's passed here and then stored, and not leaked to a
// background thread simultaneously, it's considered safe.
self.currentSettings = settings
}
}
func setup() async {
let mySettings = UserSettings()
let manager = SettingsManager()
// This looks like a Sendability violation, but the compiler
// identifies an isolation region transfer.
await manager.applySettings(mySettings)
}
In this snippet, UserSettings is a plain class—definitely not Sendable. In older versions of Swift, await manager.applySettings(mySettings) would have triggered a warning. But now, the compiler tracks the "region." It sees that mySettings is created in the setup function and then handed over to the SettingsManager actor. Because there's no way for setup to keep mutating mySettings while SettingsManager is using it, the isolation is preserved.
Where the Region Breaks
The magic fails the moment you try to "leak" the object out of that tracked briefcase. The compiler is great at tracking linear transfers, but it can't track objects that escape into closures that might run whenever, wherever.
I like to think of this as leaving the briefcase open in a public hallway. Suddenly, the security guard (the compiler) has no idea who is touching the document.
actor SettingsManager {
func processSettings(_ settings: UserSettings) {
// This is fine... until we do this:
Task {
// ERROR: Capture of 'settings' with non-sendable type 'UserSettings'
// in a @Sendable closure
print("Processing \(settings.theme) in background")
}
}
}
Inside the Task closure, we've broken the isolation region. Task creates a new asynchronous context that is @Sendable. Because the closure could run at any time on any thread, the compiler can no longer guarantee that the settings object isn't being modified elsewhere. The "briefcase" has been opened, and the Sendable checking kicks back in with full force.
Balancing Strictness and Flexibility
You might be tempted to just mark everything as @unchecked Sendable to make these warnings go away. Please, don't. That's essentially telling the compiler, "Trust me, I'm a professional," right before you accidentally introduce a data race that crashes your app in production. Isolation regions are designed to give you flexibility without sacrificing safety. When you see a Sendable warning despite using isolation regions, it's usually a sign that your data flow is too complex or your closures are escaping in ways that could actually cause a crash.
📋 Practical Task
Fixing the Leaky Database Connection
You are working on a legacy codebase where a DatabaseConnection class is not Sendable because it manages a raw C-pointer to a socket. You have a DatabaseCoordinator actor that is supposed to handle these connections.
The following code currently fails to compile in Swift 6 mode because the isolation region is being breached. Your task is to modify the DatabaseCoordinator so that the connection is handled within the actor's isolation region without using @unchecked Sendable.
class DatabaseConnection {
var connectionId: Int = 123
func query() { print("Querying database...") }
}
actor DatabaseCoordinator {
func performQuery(with connection: DatabaseConnection) {
// BUG: This closure breaks the isolation region
Task {
connection.query()
}
}
}
// Execution context
func runDatabaseTask() async {
let conn = DatabaseConnection()
let coordinator = DatabaseCoordinator()
await coordinator.performQuery(with: conn)
}
Your Goal: Modify performQuery so that it executes the query without breaking the isolation region. Hint: Think about whether the work actually needs to be wrapped in a new Task, or if it can be performed directly within the actor's current isolated context.
There are no comments for now.