Equatable Macros

repository·main·Indexed 42 days ago

https://github.com/ordo-one/equatable

A Swift package providing macros to generate optimized Equatable and Hashable conformances for structs. It is specifically designed for SwiftUI performance by automatically skipping property wrappers like @State and @Environment, and providing tools like @EquatableIgnored and @EquatableIgnoredUnsafeClosure to manage property comparison and closure handling.

Tokens
1.4K
Snippets
5
Records
5
Agent score
4%

What's inside Equatable Macros

  1. Configure Equatable isolation levels

    main

    The @Equatable macro supports an isolation parameter to control the concurrency context of the generated == and hash(into:) methods. This parameter also applies to Hashable conformance if the type conforms to it.

    Available options:

    • .nonisolated (default): The conformance can be called from any context.
    • .isolated: The conformance can only be called from within the actor's context.
    • .main (requires Swift 6.2+): The conformance is bound to the @MainActor. This allows the generated code to access @MainActor isolated properties synchronously.
    // Nonisolated (default)
    @Equatable(isolation: .nonisolated)
    struct Person {
        let name: String
    }
    
    // Isolated
    @Equatable(isolation: .isolated)
    struct Person {
        let name: String
    }
    
    // Main (Swift 6.2+)
    @Equatable(isolation: .main)
    @MainActor
    struct Person {
        let name: String
    }
  2. Exclude closures safely with @EquatableIgnoredUnsafeClosure

    main

    Because closures cannot be easily compared, you can use @EquatableIgnoredUnsafeClosure to exclude them from equality checks.

    Warning: You must ensure the closure does not capture value types that affect the view's identity or rendering at the call site. If a closure captures a value (like a @State variable) and you ignore it in Equatable, the view may fail to re-render when that captured value changes, leading to stale UI.

    // SAFE: The closure does not capture value types that influence rendering
    struct UserActions: Equatable {
        let id: UUID
        @EquatableIgnoredUnsafeClosure
        var onTap: () -> Void
    }
    
    // UNSAFE: The closure captures 'enabled', but because the closure is ignored,
    // the view won't re-render when 'enabled' changes.
    struct ViewTakesClosure: View {
        @State var enabled = false
        @EquatableIgnoredUnsafeClosure let onTapGesture: () -> Void
        // ...
    }
  3. Install Equatable via Swift Package Manager

    main

    Add the equatable package to your project's dependencies and include the Equatable product in your target.

    dependencies: [
        .package(url: "https://github.com/ordo-one/equatable.git", from: "1.0.0")
    ]
    
    // In your target definition:
    .target(
        name: "YourTarget",
        dependencies: [
            .product(name: "Equatable", package: "equatable")
        ]
    )
  4. Use the @Equatable macro to generate conformance

    main

    Apply the @Equatable macro to a struct to automatically generate an optimized Equatable implementation.

    Behavioral Rules:

    • Automatic Inclusion: All stored instance properties are compared.
    • Automatic Exclusion: SwiftUI property wrappers like @State and @Environment are automatically skipped.
    • Manual Exclusion: Use @EquatableIgnored to exclude specific properties.
    • Closure Handling: Closures are excluded by default but must be explicitly marked with @EquatableIgnoredUnsafeClosure to be ignored in comparisons.
    import Equatable
    import SwiftUI
    
    @Equatable
    struct ProfileView: View {
        var username: String   // Compared
        @State private var isLoading = false           // Automatically skipped
        @ObservedObject var viewModel: ProfileViewModel // Automatically skipped
        @EquatableIgnored var cachedValue: String?    // Excluded
        @EquatableIgnoredUnsafeClosure var onTap: () -> Void // Excluded
        let id: UUID // Compared first for short-circuiting
        
        var body: some View {
            VStack {
                Text(username)
                if isLoading {
                    ProgressView()
                }
            }
        }
    }
  5. Generate Hashable conformance with @Equatable

    main

    If a struct is marked as Hashable, the @Equatable macro will automatically generate a Hashable implementation that is perfectly aligned with the generated Equatable logic. This ensures that a == b implies a.hashValue == b.hashValue even when properties are ignored via @EquatableIgnored.

    import Equatable
    
    @Equatable
    struct User: Hashable {
        let id: Int
        @EquatableIgnored var name = ""
    }
    
    // The macro ensures 'name' is excluded from both == and hash(into:)