If you are an iOS Developer looking to take your skills to the next level, you have probably encountered performance issues: stuttering animations, views reloading for no reason, and excessive CPU usage. In the Apple ecosystem, efficiency is key.
In this article, we are going to dive deep into one of the most fundamental and powerful concepts in Swift programming: the Equatable protocol in SwiftUI. We will learn not only what it is, but how to implement it step-by-step in Xcode to optimize your SwiftUI applications across multiple platforms, including iOS, macOS, and watchOS.
What is the Equatable protocol in Swift programming?
In its most basic form, the Equatable protocol in Swift allows two instances of the same type to be compared to know if they are “equal” or “different”.
When you use the equality (==) or inequality (!=) operators, you are using the Equatable protocol under the hood. If you create a custom struct or class in Swift and do not conform to this protocol, the compiler won’t know how to compare two instances of your model and will throw an error.
The anatomy of Equatable
The protocol itself is incredibly simple. It only requires the implementation of a static function:
static func == (lhs: Self, rhs: Self) -> Bool
lhs(Left Hand Side): The value on the left side of the==operator.rhs(Right Hand Side): The value on the right side of the==operator.
Automatic Synthesis in Swift
One of the great advantages of modern Swift programming is that, in most cases, you don’t have to write this function manually. If you have a Struct where all its properties already conform to the Equatable protocol (like String, Int, Bool, etc.), Swift automatically synthesizes the implementation for you.
// Swift generates the == function automatically
struct User: Equatable {
let id: UUID
let name: String
let age: Int
}
let user1 = User(id: UUID(), name: "Anna", age: 28)
let user2 = User(id: UUID(), name: "Anna", age: 28)
// This is possible thanks to Equatable
if user1 == user2 {
print("They are exactly equal")
}
Manual Implementation: Taking Control
Sometimes, automatic synthesis isn’t what you need. As an iOS Developer, you will encounter situations where two objects must be considered “equal” based solely on a unique identifier, even if other properties (like a temporary UI state) have changed.
struct BlogPost: Equatable {
let id: String
var title: String
var reads: Int
// Manual implementation
static func == (lhs: BlogPost, rhs: BlogPost) -> Bool {
// We only care that the ID is the same to consider them the same article
return lhs.id == rhs.id
}
}
By doing this, you tell Swift exactly under what rules two instances are identical. This becomes crucial when working with reactive user interfaces.
Equatable protocol in SwiftUI: The Key to Performance
To understand why the Equatable protocol in SwiftUI is so vital, we need to understand how SwiftUI draws screens.
SwiftUI is a state-based declarative framework. When a view’s state changes (for example, via @State or @ObservedObject), SwiftUI evaluates the view hierarchy, compares the new view with the old view, and calculates which parts of the screen need to be redrawn. This process is known as diffing.
The problem with unnecessary reloads
Imagine you have a parent view that updates a temporary counter every second, but also contains a heavy child view displaying a user’s profile. By default, when the parent updates, the child view might also be re-evaluated.
The solution: .equatable()
By making the child view conform to Equatable and applying the .equatable() modifier, you are telling SwiftUI: “Hey, before spending resources redrawing this view, compare its current state with the new one using the == function. If it returns true, skip it and don’t redraw it”.
Xcode Tutorial Guide: Multiplatform (iOS, macOS, watchOS)
Next, we are going to create a small project in Xcode that demonstrates how to use this in practice. The code we write will work perfectly on iOS, macOS, and watchOS.
Step 1: Set Up the Model
First, we create our data model. We ensure it is Equatable.
import Foundation
struct Product: Identifiable, Equatable {
let id: UUID
let name: String
var price: Double
var inStock: Bool
// We opt for Swift's automatic synthesis.
// Two products are equal if ALL their properties are equal.
}
Step 2: Create the heavy child view (Equatable View)
Now, in Xcode, we create a view that represents our product cell. This view will simulate having complex rendering (for example, loading heavy images or doing calculations).
import SwiftUI
struct ProductCellView: View, Equatable {
let product: Product
var body: some View {
// We simulate a log to see when SwiftUI actually redraws this view
let _ = print("Redrawing ProductCellView for: \(product.name)")
VStack(alignment: .leading) {
Text(product.name)
.font(.headline)
Text("Price: $\(product.price, specifier: "%.2f")")
.foregroundColor(.secondary)
if product.inStock {
Text("In Stock")
.font(.caption)
.padding(4)
.background(Color.green.opacity(0.2))
.cornerRadius(4)
} else {
Text("Out of Stock")
.font(.caption)
.padding(4)
.background(Color.red.opacity(0.2))
.cornerRadius(4)
}
}
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(10)
}
// Explicit manual implementation for SwiftUI
// If the product is the same, the view is the same and shouldn't be redrawn.
static func == (lhs: ProductCellView, rhs: ProductCellView) -> Bool {
return lhs.product == rhs.product
}
}
Step 3: The parent view and optimization
Now we create the main view. We’ll have a button that changes a state irrelevant to the products (like a color theme or a timer), to demonstrate how we prevent ProductCellView from reloading.
struct CatalogView: View {
@State private var products: [Product] = [
Product(id: UUID(), name: "MacBook Pro", price: 2499.0, inStock: true),
Product(id: UUID(), name: "iPhone 15 Pro", price: 999.0, inStock: true),
Product(id: UUID(), name: "Apple Watch Ultra", price: 799.0, inStock: false)
]
// A state that changes frequently but doesn't affect the products
@State private var irrelevantCounter: Int = 0
var body: some View {
ScrollView {
VStack(spacing: 20) {
Text("Click counter: \(irrelevantCounter)")
.font(.title)
Button("Update Counter") {
irrelevantCounter += 1
}
.buttonStyle(.borderedProminent)
Divider()
ForEach(products) { product in
// HERE IS THE MAGIC!
// We use .equatable() so SwiftUI uses our == method
ProductCellView(product: product)
.equatable()
}
}
.padding()
}
}
}
What happens when compiling in Xcode?
- Without
.equatable(): Every time you press “Update Counter”, theirrelevantCounterstate changes. SwiftUI re-evaluatesCatalogView. You will see in the Xcode console that the message “Redrawing ProductCellView…” is printed several times per click, wasting CPU cycles. - With
.equatable(): By pressing the button, the parent changes, but when SwiftUI reachesProductCellView, it executes the==function. Since the properties of theproducthaven’t changed, the function returnstrue. SwiftUI stops the update on that branch of the view tree. The console remains clean. Performance optimized!
Advanced Use Cases and Common Pitfalls
Like any good iOS Developer, you should know that this tool is not a silver bullet. Use it with caution following these rules:
1. Do not optimize prematurely
The SwiftUI diffing engine is already extremely fast and efficient. You do not (and should not) apply .equatable() to every small Text or Image in your application. Use the Equatable protocol in SwiftUI only when you identify a real performance bottleneck, typically in views that:
- Contain a lot of heavy mathematical logic in their
body. - Render complex graphics.
- Are inside a massive
ScrollViewor aListwith thousands of elements that change very frequently.
2. Beware of Reference Types (Classes)
In Swift programming, if your model is a class instead of a struct, the == comparison can be misleading if you compare memory references (using ===) instead of actual values. For SwiftUI, it is highly recommended to use Structs (Value Types) for UI data.
3. @Binding and Equatable
Comparing views that contain @Binding can be problematic. A Binding does not easily conform to Equatable because it represents a bidirectional connection, not just a static value. If you need to make an equatable view that uses bindings, it is often better to extract the underlying value or redesign the data flow to avoid passing complex bindings to views that require high rendering optimization.
Conclusion
Mastering the Equatable protocol in SwiftUI and Swift programming in general is an essential step for any iOS Developer aspiring to create professional-quality applications. Understanding how Xcode compiles these instructions and how SwiftUI decides what to paint on the screen gives you the power to create fluid interfaces, maximizing the battery life of iOS, macOS, and watchOS devices.
If you have any questions about this article, please contact me and I will be happy to help you . You can contact me on my X profile or on my Instagram profile.