Swift in 2025: What's New in Practice?
2025 has become a landmark year for the Apple ecosystem. Swift 6 has been released — a release that promises not only stability but also fundamental changes in how we write code. If you are an iOS or macOS developer, you need to understand the innovations to keep up with the industry. In this article, we will break down the key changes in Swift 6 with practical examples that you can immediately apply in your projects.
1. Non-exhaustive Enums
One of the most anticipated changes is the ability to declare enums that can be extended in the future without breaking backward compatibility. Previously, if a library added a new case to an enum, your code would break. That is now a thing of the past.
Declaring such an enum looks like this:
public enum NetworkError: Sendable { case timeout case serverError(Int) case unknown @_nonExhaustive case custom(String)}When handling such an enum, the compiler no longer requires the switch to be exhaustive. You must add a default case:
func handleError(_ error: NetworkError) { switch error { case .timeout: print("Timeout") case .serverError(let code): print("Server error: \\(code)") default: print("Unknown error") }}Practical tip: If you are developing a public library or SDK, mark your enums with the @_nonExhaustive attribute — this will maintain compatibility with future versions of your code.
2. Macros: A New Level of Metaprogramming
Macros, introduced in Swift 5.9, have become truly powerful in 2025. Swift 6 introduces attribute macros and member macros, which allow you to generate code at compile time without unnecessary boilerplate.
Consider an example: instead of manually writing init and Codable for a model, you can use the built-in @MemberwiseInit macro (new in Swift 6):
@MemberwiseInitstruct UserProfile: Codable { let id: Int var name: String var email: String var age: Int}
// The compiler automatically generates:// init(id: Int, name: String, email: String, age: Int)// and the Codable implementationAnother example is the #Predicate macro for SwiftData:
let youngUsers = try modelContext.fetch( FetchDescriptor<UserProfile>( predicate: #Predicate { $0.age < 25 } ))Macros significantly reduce the amount of boilerplate code and decrease the likelihood of errors. In 2025, their use has become the standard for new projects.
3. Observable Properties and SwiftUI
Swift 6 introduces a new Observable type, which replaces ObservableObject from Combine. Now you don't need to write @Published and @ObservedObject — everything works through the @Observable macro.
Example model:
import Observation
@Observableclass CounterViewModel { var count = 0 var isPremium = false
func increment() { count += 1 if count >= 10 { isPremium = true } }}Usage in SwiftUI has become simpler:
struct CounterView: View { @State private var viewModel = CounterViewModel()
var body: some View { VStack { Text("Count: \\(viewModel.count)") Button("Increment") { viewModel.increment() } if viewModel.isPremium { Text("Premium available!") } } .padding() }}What changed?
Blogs
Book Recommendations
Read also