| name | swift-fundamentals |
| description | Swift 6.x language best practices, macros, project setup, and modern patterns. Use when user asks about Swift syntax, Package.swift configuration, project structure, macros like @Observable/@Model/@MainActor, property wrappers, or modern Swift development patterns. |
| allowed-tools | Bash, Read, Write, Edit |
Swift Fundamentals
Comprehensive guide to Swift 6.x language features, best practices, macros, and project configuration for iOS 26 and macOS Tahoe development.
Prerequisites
- Xcode 26+ or Swift 6.x toolchain
- macOS 15.5+ (Sequoia) for full iOS 26 support
Swift Version Check
swift --version
# Swift version 6.x
Swift 6.x Key Features
Swift 6.0 - Major Changes
- Complete Concurrency Checking - Strict data-race safety by default
- Typed Throws - Functions can specify error types:
throws(MyError) - Noncopyable Types -
~Copyablefor unique ownership - Pack Iteration - Iterate over parameter packs
- 128-bit Integer Types -
Int128andUInt128 - count(where:) - Count elements matching predicate
Swift 6.2 - Recent Improvements
- Approachable Concurrency - Easier adoption path
- Single-threaded by default - New
defaultIsolationsetting - Observations Async Sequence - Stream state changes
- Pre-built swift-syntax - Faster macro compilation
- InlineArray - Fixed-size inline storage
Swift 6.3 - Latest (2025)
- Enhanced Type Inference - Better generic inference
- Improved Error Messages - Clearer diagnostics
- Performance Optimizations - Faster compilation
Core Macros
@Observable (iOS 17+)
Modern replacement for ObservableObject. Automatically tracks property access.
import Observation
@Observable
class UserSettings {
var username: String = ""
var isLoggedIn: Bool = false
var preferences: Preferences = Preferences()
// Computed properties are also tracked
var displayName: String {
isLoggedIn ? username : "Guest"
}
}
// Usage in SwiftUI
struct SettingsView: View {
var settings: UserSettings // No @ObservedObject needed
var body: some View {
Text(settings.displayName)
// View automatically updates when displayName changes
}
}
Key Differences from ObservableObject:
- No
@Publishedproperty wrappers needed - No
objectWillChangepublisher - Finer-grained updates (per-property, not whole object)
- Use
@Bindablefor two-way bindings
struct EditView: View {
@Bindable var settings: UserSettings
var body: some View {
TextField("Username", text: $settings.username)
}
}
@Model (SwiftData)
Declares a SwiftData model with automatic persistence.
import SwiftData
@Model
class Note {
var title: String
var content: String
var createdAt: Date
var tags: [Tag]? // Optional relationship for CloudKit
init(title: String, content: String = "") {
self.title = title
self.content = content
self.createdAt = Date()
}
}
@Model
class Tag {
var name: String
var notes: [Note]? // Inverse relationship
init(name: String) {
self.name = name
}
}
Important for CloudKit Sync:
- All relationships must be optional
- No
@Attribute(.unique)constraints - Default values required for non-optional properties
@MainActor
Ensures code runs on the main thread/actor.
@MainActor
class ViewModel {
var items: [Item] = []
var isLoading = false
func loadItems() async {
isLoading = true
defer { isLoading = false }
// Network call can be off main actor
let fetched = await fetchItems()
// Assignment happens on main actor
items = fetched
}
nonisolated func fetchItems() async -> [Item] {
// This can run on any thread
try? await URLSession.shared.data(from: url)
// ...
}
}
@Generable (Foundation Models - iOS 26)
Enables structured AI output generation.
import FoundationModels
@Generable
struct MovieRecommendation {
var title: String
var year: Int
var genre: String
var reason: String
}
// Usage
let session = LanguageModelSession()
let recommendation: MovieRecommendation = try await session.respond(
to: "Recommend a sci-fi movie from the 2020s"
)
@Test (Swift Testing)
Marks a function as a test case.
import Testing
@Test("User can create account with valid email")
func createAccountWithValidEmail() async throws {
let result = try await authService.createAccount(email: "test@example.com")
#expect(result.success)
#expect(result.user?.email == "test@example.com")
}
@Test("Password validation", arguments: [
("abc", false),
("abc12345", true),
("ABC12345!", true)
])
func validatePassword(password: String, expected: Bool) {
#expect(validator.isValid(password) == expected)
}
Property Wrappers
SwiftUI Property Wrappers
struct ContentView: View {
// Local state - view owns this value
@State private var count = 0
// Two-way binding from parent
@Binding var selectedTab: Int
// Environment value from system or parent
@Environment(\.colorScheme) var colorScheme
// Custom environment object
@Environment(AppState.self) var appState
// SwiftData query
@Query(sort: \Note.createdAt, order: .reverse)
var notes: [Note]
// Focus state for text fields
@FocusState private var isFocused: Bool
// Namespace for matched geometry effects
@Namespace private var animation
var body: some View {
// ...
}
}
@Bindable (iOS 17+)
Creates bindings to @Observable properties.
struct EditorView: View {
@Bindable var document: Document // Document is @Observable
var body: some View {
TextEditor(text: $document.content)
Toggle("Published", isOn: $document.isPublished)
}
}
@AppStorage
Persists values to UserDefaults.
struct SettingsView: View {
@AppStorage("hasCompletedOnboarding") var hasCompletedOnboarding = false
@AppStorage("preferredTheme") var preferredTheme = "system"
@AppStorage("notificationsEnabled") var notificationsEnabled = true
var body: some View {
Toggle("Notifications", isOn: $notificationsEnabled)
}
}
@SceneStorage
Persists view state per scene (for state restoration).
struct DocumentView: View {
@SceneStorage("selectedDocumentID") var selectedDocumentID: String?
@SceneStorage("scrollPosition") var scrollPosition: Double = 0
var body: some View {
// State restored when scene is recreated
}
}
Package.swift Configuration
Basic Package
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "MyApp",
platforms: [
.iOS(.v26),
.macOS(.v26)
],
products: [
.library(name: "MyApp", targets: ["MyApp"])
],
dependencies: [
// External dependencies
],
targets: [
.target(
name: "MyApp",
dependencies: [],
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency")
]
),
.testTarget(
name: "MyAppTests",
dependencies: ["MyApp"]
)
]
)
With Dependencies
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "MyApp",
platforms: [
.iOS(.v26),
.macOS(.v26)
],
products: [
.library(name: "MyApp", targets: ["MyApp"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0"),
.package(url: "https://github.com/apple/swift-collections", from: "1.1.0"),
.package(url: "https://github.com/apple/swift-async-algorithms", from: "1.0.0")
],
targets: [
.target(
name: "MyApp",
dependencies: [
.product(name: "Algorithms", package: "swift-algorithms"),
.product(name: "Collections", package: "swift-collections"),
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms")
]
)
]
)
Strict Concurrency Settings
swiftSettings: [
// Full Swift 6 concurrency
.enableExperimentalFeature("StrictConcurrency"),
// Or gradual adoption
.swiftLanguageMode(.v6),
// Enable upcoming features
.enableUpcomingFeature("ExistentialAny"),
.enableUpcomingFeature("InternalImportsByDefault")
]
Project Structure
Recommended Layout
MyApp/
├── Package.swift
├── Sources/
│ └── MyApp/
│ ├── App/
│ │ ├── MyAppApp.swift
│ │ └── AppState.swift
│ ├── Models/
│ │ ├── Note.swift
│ │ ├── Tag.swift
│ │ └── User.swift
│ ├── Views/
│ │ ├── ContentView.swift
│ │ ├── Notes/
│ │ │ ├── NoteListView.swift
│ │ │ ├── NoteDetailView.swift
│ │ │ └── NoteEditorView.swift
│ │ └── Settings/
│ │ └── SettingsView.swift
│ ├── Services/
│ │ ├── NetworkService.swift
│ │ └── StorageService.swift
│ ├── Utilities/
│ │ ├── Extensions/
│ │ └── Helpers/
│ └── Resources/
│ ├── Localizable.xcstrings
│ └── Assets.xcassets
└── Tests/
└── MyAppTests/
├── ModelTests/
├── ServiceTests/
└── ViewTests/
App Entry Point
import SwiftUI
import SwiftData
@main
struct MyAppApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(for: [Note.self, Tag.self])
}
}
With Custom Container Configuration
@main
struct MyAppApp: App {
let container: ModelContainer
init() {
let schema = Schema([Note.self, Tag.self])
let config = ModelConfiguration(
schema: schema,
isStoredInMemoryOnly: false,
cloudKitDatabase: .automatic // Enable iCloud sync
)
do {
container = try ModelContainer(for: schema, configurations: config)
} catch {
fatalError("Failed to configure SwiftData: \(error)")
}
}
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(container)
}
}
Modern Swift Patterns
Result Builders
@resultBuilder
struct HTMLBuilder {
static func buildBlock(_ components: String...) -> String {
components.joined()
}
static func buildOptional(_ component: String?) -> String {
component ?? ""
}
static func buildEither(first component: String) -> String {
component
}
static func buildEither(second component: String) -> String {
component
}
}
func html(@HTMLBuilder content: () -> String) -> String {
"<html>\(content())</html>"
}
let page = html {
"<head><title>Hello</title></head>"
"<body>"
if showHeader {
"<h1>Welcome</h1>"
}
"<p>Content here</p>"
"</body>"
}
Typed Throws (Swift 6)
enum NetworkError: Error {
case invalidURL
case noData
case decodingFailed
}
func fetchUser(id: Int) throws(NetworkError) -> User {
guard let url = URL(string: "https://api.example.com/users/\(id)") else {
throw .invalidURL
}
// ...
}
// Caller knows exactly what errors to handle
do {
let user = try fetchUser(id: 123)
} catch .invalidURL {
// Handle invalid URL
} catch .noData {
// Handle no data
} catch .decodingFailed {
// Handle decoding error
}
Noncopyable Types (Swift 6)
struct UniqueResource: ~Copyable {
let handle: Int
init(handle: Int) {
self.handle = handle
}
deinit {
// Clean up resource
closeHandle(handle)
}
consuming func close() {
// Explicitly consume the resource
}
}
func useResource() {
let resource = UniqueResource(handle: 42)
// resource cannot be copied
// processResource(resource) // This moves resource
// resource.doSomething() // Error: resource was moved
}
Parameter Packs (Swift 6)
func all<each T: Equatable>(
_ values: repeat each T,
equalTo comparisons: repeat each T
) -> Bool {
for (value, comparison) in repeat (each values, each comparisons) {
if value != comparison {
return false
}
}
return true
}
let result = all(1, "hello", true, equalTo: 1, "hello", true) // true
Error Handling Best Practices
Define Domain Errors
enum AppError: LocalizedError {
case networkUnavailable
case unauthorized
case notFound(resource: String)
case validationFailed(field: String, reason: String)
case unknown(underlying: Error)
var errorDescription: String? {
switch self {
case .networkUnavailable:
return "Network connection unavailable"
case .unauthorized:
return "You are not authorized to perform this action"
case .notFound(let resource):
return "\(resource) was not found"
case .validationFailed(let field, let reason):
return "\(field): \(reason)"
case .unknown(let error):
return error.localizedDescription
}
}
}
Result Type Pattern
func fetchData() async -> Result<Data, AppError> {
guard NetworkMonitor.shared.isConnected else {
return .failure(.networkUnavailable)
}
do {
let data = try await networkService.fetch()
return .success(data)
} catch {
return .failure(.unknown(underlying: error))
}
}
// Usage
switch await fetchData() {
case .success(let data):
process(data)
case .failure(let error):
showError(error)
}
Extensions Best Practices
Organize by Functionality
// String+Validation.swift
extension String {
var isValidEmail: Bool {
let pattern = #"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"#
return range(of: pattern, options: .regularExpression) != nil
}
var isValidPassword: Bool {
count >= 8 &&
range(of: "[A-Z]", options: .regularExpression) != nil &&
range(of: "[0-9]", options: .regularExpression) != nil
}
}
// Date+Formatting.swift
extension Date {
var relativeDescription: String {
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .short
return formatter.localizedString(for: self, relativeTo: Date())
}
var iso8601String: String {
ISO8601DateFormatter().string(from: self)
}
}
// View+Modifiers.swift
extension View {
func cardStyle() -> some View {
modifier(CardModifier())
}
@ViewBuilder
func `if`<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {
if condition {
transform(self)
} else {
self
}
}
}
Code Quality
SwiftLint Configuration
# .swiftlint.yml
included:
- Sources
- Tests
excluded:
- .build
- Package.swift
disabled_rules:
- trailing_whitespace
- line_length
opt_in_rules:
- empty_count
- explicit_init
- closure_spacing
- overridden_super_call
- redundant_nil_coalescing
- private_outlet
- nimble_operator
- attributes
- operator_usage_whitespace
- closure_end_indentation
- first_where
- object_literal
- number_separator
- prohibited_super_call
- fatal_error_message
- weak_delegate
line_length:
warning: 120
error: 200
type_body_length:
warning: 300
error: 500
file_length:
warning: 500
error: 1000
identifier_name:
min_length: 2
max_length: 50
Swift Format Configuration
// .swift-format
{
"version": 1,
"lineLength": 120,
"indentation": {
"spaces": 4
},
"tabWidth": 4,
"maximumBlankLines": 1,
"respectsExistingLineBreaks": true,
"lineBreakBeforeControlFlowKeywords": false,
"lineBreakBeforeEachArgument": false
}