--- name: Modern Swift description: Modern Swift language patterns, concurrency, and API usage globs: ["**/*.swift"] --- # Modern Swift Patterns Use modern Swift language features and avoid deprecated patterns. ## Concurrency ### Always Mark Observable Classes with @MainActor ```swift @Observable @MainActor final class FeatureStore { // All properties and methods run on main actor } ``` ### Use Modern Concurrency (No GCD) ```swift // BAD - Old GCD patterns DispatchQueue.main.async { self.updateUI() } DispatchQueue.global().async { let result = self.heavyWork() DispatchQueue.main.async { self.handle(result) } } // GOOD - Modern concurrency await MainActor.run { updateUI() } Task.detached { let result = await heavyWork() await MainActor.run { handle(result) } } ``` ### Use Task.sleep(for:) Not nanoseconds ```swift // BAD try await Task.sleep(nanoseconds: 1_000_000_000) // GOOD try await Task.sleep(for: .seconds(1)) try await Task.sleep(for: .milliseconds(500)) ``` ### Strict Concurrency Compliance ```swift // Ensure data crossing actor boundaries is Sendable struct Item: Sendable { let id: UUID let name: String } // Use @unchecked Sendable only when you've manually verified thread safety final class Cache: @unchecked Sendable { private let lock = NSLock() private var storage: [String: Any] = [:] } ``` ## Foundation APIs ### Use Modern URL APIs ```swift // BAD - Deprecated patterns let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let file = docs.appendingPathComponent("data.json") // GOOD - Modern APIs let docs = URL.documentsDirectory let file = docs.appending(path: "data.json") ``` ### Use Modern Date Formatting ```swift // BAD - DateFormatter let formatter = DateFormatter() formatter.dateStyle = .medium let string = formatter.string(from: date) // GOOD - format method let string = date.formatted(.dateTime.month().day().year()) let relative = date.formatted(.relative(presentation: .named)) ``` ### Use Modern Number Formatting ```swift // BAD - C-style or NumberFormatter let string = String(format: "%.2f", price) // GOOD - format method let string = price.formatted(.currency(code: "USD")) let percent = ratio.formatted(.percent.precision(.fractionLength(1))) ``` ## String Handling ### Use localizedStandardContains for User Search ```swift // BAD - Case-sensitive or manual lowercasing items.filter { $0.name.lowercased().contains(query.lowercased()) } // GOOD - Locale-aware, case/diacritic insensitive items.filter { $0.name.localizedStandardContains(query) } ``` ### String Interpolation Over Concatenation ```swift // BAD let message = "Hello, " + user.name + "!" // GOOD let message = "Hello, \(user.name)!" ``` ## Type Safety ### Avoid Force Unwraps ```swift // BAD let value = dictionary["key"]! let url = URL(string: urlString)! // GOOD - Guard or optional binding guard let value = dictionary["key"] else { throw ValidationError.missingKey } guard let url = URL(string: urlString) else { throw NetworkError.invalidURL } ``` ### Avoid Force Casts ```swift // BAD let view = cell as! CustomCell // GOOD guard let view = cell as? CustomCell else { assertionFailure("Expected CustomCell") return } ``` ### Avoid Force Try ```swift // BAD let data = try! encoder.encode(object) // GOOD - Handle the error do { let data = try encoder.encode(object) } catch { logger.error("Encoding failed: \(error)") } ``` ## Prefer Swift-Native Patterns ### Static Member Lookup ```swift // BAD - Struct instances .clipShape(RoundedRectangle(cornerRadius: 8)) // GOOD - Static member lookup .clipShape(.rect(cornerRadius: 8)) ``` ### Result Builders Over Imperative Construction ```swift // BAD - Imperative array building var views: [AnyView] = [] if showHeader { views.append(AnyView(HeaderView())) } views.append(AnyView(ContentView())) // GOOD - ViewBuilder @ViewBuilder var content: some View { if showHeader { HeaderView() } ContentView() } ``` ### KeyPath Expressions ```swift // BAD items.map { $0.name } items.sorted { $0.date < $1.date } // GOOD items.map(\.name) items.sorted(using: KeyPathComparator(\.date)) ``` ## Collections ### Use First(where:) Over Filter().first ```swift // BAD - Creates intermediate array let item = items.filter { $0.id == targetId }.first // GOOD - Short-circuits let item = items.first { $0.id == targetId } ``` ### Use Contains(where:) Over Filter().isEmpty ```swift // BAD let hasActive = !items.filter { $0.isActive }.isEmpty // GOOD let hasActive = items.contains { $0.isActive } ``` ### Use Lazy for Chained Operations ```swift // Process large collections efficiently let result = largeArray .lazy .filter { $0.isValid } .map { $0.transformed } .prefix(10) .map(Array.init) // Materialize only when needed ``` ## Error Handling ### Prefer Typed Throws (Swift 6 / Xcode 16+) Typed throws requires the **Swift 6 compiler** (shipped with Xcode 16). The feature works at any deployment target, but your project must compile with Swift 6. If your team hasn't migrated yet, continue using untyped `throws`. ```swift // Swift 6 - Typed throws enum NetworkError: Error { case notFound case unauthorized case serverError(Int) } func fetch() throws(NetworkError) -> Data { // ... } ``` ### Use Result for Async Callbacks (When Not Using async/await) ```swift func fetch(completion: @escaping (Result) -> Void) { // ... } ```