# Equatable Protocol and View

**URL:** <https://forums.swift.org/t/equatable-protocol-and-view/75935>\
**Category:** Using Swift\
**Tags:** concurrency, swiftui\
**Created:** [November 10, 2024, 12:37pm UTC](https://forums.swift.org/t/equatable-protocol-and-view/75935 "2024-11-10T12:37:28Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![jesus-mg-ios](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jesus-mg-ios/32/23184_2.png) [@jesus-mg-ios](https://forums.swift.org/u/jesus-mg-ios)\
**Post date:** [November 10, 2024, 12:37pm UTC](https://forums.swift.org/t/equatable-protocol-and-view/75935/1 "2024-11-10T12:37:28Z")

</div>

Hi Swift Community,

Thank you for taking the time to look into this topic.

I've run into a challenge when trying to make a SwiftUI view conform to `Equatable`. In Swift 6, I'm seeing warnings and errors related to nonisolated protocol conformance and `@MainActor`. The specific issue is that the `==` operator required by `Equatable` cannot be used because it conflicts with the actor isolation of the view.

### Main Questions:

1. Is there an `Equatable`-like protocol or method specific to views that works within the constraints of `@MainActor`?
2. Are there any known workarounds or best practices for comparing views that are actor-isolated without breaking thread safety or concurrency rules?

Any guidance or suggestions would be greatly appreciated!

---

<div class="post-metadata">

**Author:** ![JuneBash](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/junebash/32/19323_2.png) [@JuneBash](https://forums.swift.org/u/JuneBash)\
**Post date:** [November 10, 2024, 9:24pm UTC](https://forums.swift.org/t/equatable-protocol-and-view/75935/2 "2024-11-10T21:24:37Z")

</div>

My question would be, _why_ are you trying to make a SwiftUI view conform to `Equatable`? I ask because I've never come across a scenario where I needed to do this, and I suspect that if you take a step back, there is a probably a way to accomplish your ultimate goal without going down that route.

Actor-isolated types generally can't conform to `Equatable` unless they a.) are reference types, _and_ b.) use their object identity as the basis of the conformance.

Coincidentally, PointFree recently did a short series on the topic of `Equatable` recently. I'd highly recommend it if you're up for subscribing (very worth it if you haven't). [Episode #297: Back to Basics: Equatable](https://www.pointfree.co/collections/back-to-basics/equatable-and-hashable/ep297-back-to-basics-equatable)

---

<div class="post-metadata">

**Author:** ![jesus-mg-ios](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jesus-mg-ios/32/23184_2.png) [@jesus-mg-ios](https://forums.swift.org/u/jesus-mg-ios)\
**Post date:** [November 10, 2024, 9:39pm UTC](https://forums.swift.org/t/equatable-protocol-and-view/75935/3 "2024-11-10T21:39:38Z")

</div>

### Why Make a View Equatable?

The `equatable()` view modifier ensures that SwiftUI only redraws a view when its properties have changed. This can improve performance, especially in complex UIs, by avoiding unnecessary rendering. However, for this optimization to work, the view needs to conform to `Equatable`, so SwiftUI can compare instances of it.

### Example Scenario:

Suppose you have a custom view that renders data based on an input model. By conforming the view to `Equatable`, SwiftUI can detect when the input data is the same as before and prevent the view from being redrawn, which enhances the app's efficiency.

See also: [equatable() | Apple Developer Documentation](https://developer.apple.com/documentation/swiftui/view/equatable())

---

<div class="post-metadata">

**Author:** ![JuneBash](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/junebash/32/19323_2.png) [@JuneBash](https://forums.swift.org/u/JuneBash)\
**Post date:** [November 10, 2024, 9:58pm UTC](https://forums.swift.org/t/equatable-protocol-and-view/75935/4 "2024-11-10T21:58:22Z")

</div>

I've personally never found myself needing that. Are you in a situation where you've measured performance and have reason to believe this would help?

If so... with some experimentation, I found doing something like this seemed to work well:

```swift
struct SimpleView: Equatable {
  let int: Int
  let string: String
}

extension SimpleView: View {
  var body: some View {
    Text(int, format: .number)
    Text(string)
  }
}

```

---

<div class="post-metadata">

**Author:** ![jesus-mg-ios](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jesus-mg-ios/32/23184_2.png) [@jesus-mg-ios](https://forums.swift.org/u/jesus-mg-ios)\
**Post date:** [November 10, 2024, 10:03pm UTC](https://forums.swift.org/t/equatable-protocol-and-view/75935/5 "2024-11-10T22:03:11Z")

</div>

The problem arises when you try to use a custom `Equatable` conformance in SwiftUI, especially if you only need to compare some properties or part of some properties of your view but not others. This can become complicated due to Swift's concurrency model and actor isolation.

---

<div class="post-metadata">

**Author:** ![vanvoorden](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/vanvoorden/32/33619_2.png) [@vanvoorden](https://forums.swift.org/u/vanvoorden)\
**Post date:** [November 11, 2024, 12:42am UTC](https://forums.swift.org/t/equatable-protocol-and-view/75935/6 "2024-11-11T00:42:45Z")

</div>

The workaround in [SE-0423](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0423-dynamic-actor-isolation.md) usually works for me when trying to work around SwiftUI and concurrency:

```swift
import NotMyLibrary

@MainActor
class MyViewController: ViewDelegateProtocol {
  nonisolated func respondToUIEvent() {
    MainActor.assumeIsolated {
      // implementation...   
    }
  }
}

```

You might want to try `@preconcurrency` if you are only building from 6.0 and up.

… but you are kind of assuming at this point that you _know_ the function is actually coming from `main`… we usually assume SwiftUI operates on `main`… so we should be good. But this is kind of a YOLO solution for now.

---

<div class="post-metadata">

**Author:** ![Nickolas\_Pohilets](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nickolas_pohilets/32/10659_2.png) [@Nickolas\_Pohilets](https://forums.swift.org/u/Nickolas_Pohilets)\
**Post date:** [November 11, 2024, 7:53am UTC](https://forums.swift.org/t/equatable-protocol-and-view/75935/7 "2024-11-11T07:53:55Z")

</div>

To my best knowledge this is indeed not possible in general case. Currently `nonisolated` + `Sendable` means “any isolation”, and `nonisolated` without `Sendable` means “some unknown isolation”. But there is no way to express “some isolation which is also known to be main actor”.

---

<div class="post-metadata">

**Author:** ![jesus-mg-ios](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jesus-mg-ios/32/23184_2.png) [@jesus-mg-ios](https://forums.swift.org/u/jesus-mg-ios)\
**Post date:** [November 24, 2024, 3:03pm UTC](https://forums.swift.org/t/equatable-protocol-and-view/75935/8 "2024-11-24T15:03:02Z")

</div>

Update: You cannot use `MainActor.assumeIsolated` because the render graph, which uses the `com.apple.SwiftUI.AsyncRenderer` queue, can operate off the main thread.

---

<div class="post-metadata">

**Author:** ![vanvoorden](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/vanvoorden/32/33619_2.png) [@vanvoorden](https://forums.swift.org/u/vanvoorden)\
**Post date:** [November 24, 2024, 9:50pm UTC](https://forums.swift.org/t/equatable-protocol-and-view/75935/9 "2024-11-24T21:50:30Z")

</div>

Hmm… weird… AFAIK SwiftUI `View` component types are now explicitly isolated to `MainActor`. Are you building in Swift 6 language mode? If you _are_ building in Swift 6 language mode and some SwiftUI infra is calling to your `View` off-main in an unsafe way… this might potentially be a bug in the SwiftUI infra. Have you searched through Apple Dev Forums for anything similar that has been posted in the last few months?

---

<div class="post-metadata">

**Author:** ![mbrandonw](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mbrandonw/32/349_2.png) [@mbrandonw](https://forums.swift.org/u/mbrandonw)\
**Post date:** [November 24, 2024, 10:42pm UTC](https://forums.swift.org/t/equatable-protocol-and-view/75935/10 "2024-11-24T22:42:02Z")

</div>

> [@vanvoorden](#):
>
> Hmm… weird… AFAIK SwiftUI `View` component types are now explicitly isolated to `MainActor`.

While it is true that SwiftUI views are `@MainActor` bound, the protocol requirements of `Equatable` are non-isolated, and so SwiftUI would be allowed to invoke `==` on a background thread if it wanted without breaking any contracts. And if done, then `assumeIsolated` would crash. So I personally would stay away from trying to conform views to `Equatable` based on the mutable data they hold.

But it's also worth mentioning that there are some strange soundness things happening inside SwiftUI. Many of the APIs in SwiftUI deal with non-isolated `@escaping` closures. For example, the [trailing closure for `ForEach`](https://developer.apple.com/documentation/swiftui/foreach/init(_:content:)-6oy5i). If one wraps a `ForEach` in a lazy view, such as `LazyVStack`, then it is possible for the `ForEach` trailing closure to be invoked on a background thread, and in particular from the `com.apple.SwiftUI.AsyncRenderer` queue.

I have not yet tracked down how exactly this is possible. It may be a soundness hole in Swift left open for SwiftUI, or perhaps there is just something I am missing. However, you can see for yourself that it is completely possible to create SwiftUI views on background threads:

```swift
DispatchQueue.global().async {
  print(
    Text("Hi"),
    "Thread is main?",
    Thread.isMainThread
  )
}

```

This creates a `Text` view on a background thread, even though the initializer should be `@MainActor` bound and the [initializer](https://github.com/xybp888/iOS-SDKs/blob/f5c585b443f1e0b5c6ced2732aac183e71021c86/iPhoneOS18.1.sdk/System/Library/Frameworks/SwiftUICore.framework/Modules/SwiftUICore.swiftmodule/arm64-apple-ios.swiftinterface#L15755-L15757) is not marked as `nonisolated`. And this compiles just fine with no warnings even in Swift 6 language mode.

It's worth noting that views we create outside of `SwiftUI` are not given such affordances:

```swift
struct MyView: View {
  init() {}
  var body: some View { EmptyView() }
}

DispatchQueue.global().async {
  print(
    MyView(), // 🛑 Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context
    "Thread is main?",
    Thread.isMainThread
  )
}

```

This does correctly diagnose that we are trying to create something `@MainActor` bound in a non-`@MainActor` context. But if you mark the initializer as `nonisolated` it will compile.

---

<div class="post-metadata">

**Author:** ![haifengkao](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/haifengkao/32/8837_2.png) [@haifengkao](https://forums.swift.org/u/haifengkao)\
**Post date:** [April 6, 2025, 8:44am UTC](https://forums.swift.org/t/equatable-protocol-and-view/75935/11 "2025-04-06T08:44:26Z")

</div>

Have you ever used

```swift
Prevents the view from updating its child view when its new value is the same as its old value.

nonisolated
func equatable() -> EquatableView<Self>

Available when `Self` conforms to `Equatable`.

```

This is the most self-contradictory s\*\*t in my swift life.

> **[equatable() | Apple Developer Documentation](https://developer.apple.com/documentation/swiftui/view/equatable())**
>
> Prevents the view from updating its child view when its new value is the same as its old value.
