# Understanding when SwiftUI re-renders an @Observable

**URL:** <https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876>\
**Category:** Using Swift\
**Tags:** observable\
**Created:** [February 14, 2025, 11:32am UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876 "2025-02-14T11:32:33Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Lupurus](https://avatars.discourse-cdn.com/v4/letter/l/e274bd/32.png) [@Lupurus](https://forums.swift.org/u/Lupurus)\
**Post date:** [February 14, 2025, 11:32am UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876/1 "2025-02-14T11:32:33Z")

</div>

I have a question according to the function of the `@Observable`-macro:

I am using an @Observable object as a source of truth in my view. My `class` has an array, but I would like to reorganize this data in my view. I was asking myself, what would be the best way to do that and I came up with the idea of using a computed property:

```swift
@Observable
class MyObservable {
	var items = [0, 1, 2, 3, 4]
}

struct TestView: View {
	var myObservable = MyObservable()
	
	var filtered: [Int] {
		myObservable.items.filter { $0 % 2 == 0 }
	}
	
	var body: some View {
		VStack(alignment: .leading) {
			Text("\(filtered)")
		}
	}
}

```

This works as pretended. But I was asking myself: Will this view be re-rendered, when any other property in the class will be changed?

I tested it like this:

```swift
@Observable
class MyObservable {
	var items = [0, 1, 2, 3, 4]
	var temp = 1
}

struct TestView: View {
	var myObservable = MyObservable()
	
	var filtered: [Int] {
		print("rerenderred")
		return myObservable.items.filter { $0 % 2 == 0 }
	}
	
	var body: some View {
		VStack(alignment: .leading) {
			Text("\(filtered)")
			
			Text("\(myObservable.temp)")
			Button("Increase Counter") {
				// question: does this lead to a re-render?
				myObservable.temp += 1
			}
			
			Button("test") {
				// this of course will lead to a re-render
				myObservable.items.append(myObservable.items.last!+1)
			}
		}
	}
}

```

Unfortunately, every time I press the button "Increase counter", I get the print "rerendered". So the computed property will be calculated again, altough the property `items` will not be changed after clicking the button.

I guess I could change this behaviour by using `.onChange(of: myObservable.items ...`?

The other idea is to take out the computed property to another view:

```swift
@Observable
class MyObservable {
	var items = [0, 1, 2, 3, 4]
	var temp = 1
}

struct TmpView: View {
	
	var myObservable: MyObservable
	
	var filtered: [Int] {
		print("rerenderred")
		return myObservable.items.filter { $0 % 2 == 0 }
	}
	
	var body: some View {
		Text("\(filtered)")
	}
}

struct TestView: View {

	var myObservable = MyObservable()

	var body: some View {
		VStack(alignment: .leading) {
			TmpView(myObservable: myObservable)
			
			Text("\(myObservable.temp)")
			Button("Increase Counter") {
				// this does NOT lead to a re-render of TmpView
				myObservable.temp += 1
			}
			
			Button("test") {
				myObservable.items.append(myObservable.items.last!+1)
			}
		}
	}
}

```

But is there maybe a more elegant way?

---

<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:** [February 14, 2025, 4:27pm UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876/2 "2025-02-14T16:27:48Z")

</div>

Computed properties that perform linear-time work can be good candidates for memoization or caching. Filtering is a pure function. Two equal inputs should produce two equal outputs. You might want to explore a property wrapper that takes an array as input and produces the filtered array as output.

---

<div class="post-metadata">

**Author:** ![Lupurus](https://avatars.discourse-cdn.com/v4/letter/l/e274bd/32.png) [@Lupurus](https://forums.swift.org/u/Lupurus)\
**Post date:** [February 14, 2025, 5:33pm UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876/3 "2025-02-14T17:33:07Z")

</div>

Hi, thanks for your answer and the idea. But if I understand it correct, your solution is not possible, because you cannot apply a property wrapper to a property of an @Observable, as this macro turns all the properties to computed properties what leads to the error: `Property wrapper cannot be applied to a computed property`

---

<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:** [February 14, 2025, 5:50pm UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876/4 "2025-02-14T17:50:44Z")

</div>

> [@Lupurus](#):
>
> you cannot apply a property wrapper to a property of an @Observable

Other way around. A property wrapper _contains_ an `Observable`. A change on the `Observable` recomputes your component `body`. That either recomputes your derived data or returns your cached results. I assume you would want `State` to persist your data across multiple component values.

FWIW this is more valuable when the derived data is greater than linear time… like sorting. The equality operator on the input array would be linear-time in the worst case… so you are performing a linear-time operation to prevent a linear-time operation from taking place. But you do get constant-time equality operations when these array point to the same copy-on-write data storage object reference.

---

<div class="post-metadata">

**Author:** ![mattcurtis](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mattcurtis/32/18493_2.png) [@mattcurtis](https://forums.swift.org/u/mattcurtis)\
**Post date:** [February 14, 2025, 7:40pm UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876/5 "2025-02-14T19:40:22Z")

</div>

To explain what you're seeing when you move filtering into `TmpView`, there's one big difference between it and its parent `TestView`: `TmpView` isn't paying attention to the value of `temp`.

SwiftUI records accesses to the properties of Observable objects during evaluations of `body`, and _only_ re-renders that view if a property it used actually changes. `TmpView` isn't using `temp`, so it doesn't bother re-rendering that view. In this case that has the side-effect of saving you the extra filtering.

As for the question of optimizing the filtering of values, in my opinion this all comes down to _who_ (as in what view) needs to use them and _when_. For small amounts of data honestly this probably doesn't matter, but I'm assuming in your real use case it does.

If you know that you will always need the filtered values whenever `MyObservable.items` changes, the solution there could be to move that work into `MyObservable`, which would mean you could consistently enforce a contract ("when `items` changes filter it") and access the filtered results anywhere you have `MyObservable`:

```swift
@Observable
class MyObservable {
    
    private(set) var filteredItems: [Int] = []
    
    var items: [Int] = [] {
        didSet {
            filteredItems = items.filter { $0 % 2 == 0 }
        }
    }
    
    init() {
        self.items = [0, 1, 2, 3, 4]
        self.filteredItems = items.filter { $0 % 2 == 0 }
    }
    
}

```

(You could even go a step further and make this lazier.)

On the other hand, if you know that you'll only ever need filtering at the level of `TestView`, `View.onChange` is a decent option to only do the work of filtering when `items` changes, but really you could do that in several different ways.

---

<div class="post-metadata">

**Author:** ![shawnington](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/shawnington/32/39455_2.png) [@shawnington](https://forums.swift.org/u/shawnington)\
**Post date:** [February 15, 2025, 2:04am UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876/6 "2025-02-15T02:04:36Z")

</div>

To my understanding, if you actually access a property of an @Observable in a view, the entire view will be redrawn UNLESS the property is accessed in the closure of a ForEeach or Loop or a few other special cases.

If you don't want the view to re-render on the button press, you need to either abstract the incrementing into a function, or pass myObservable to another view that will re-render.

I just went through this process in my app that I updated to use @Observable, and managed to eliminate the needless redraws by accessing the properties in functions, or by just passing the observable to the view that actually need to use the property.

Since you are accessing properties of the observable in the button in the view that calls TmpView, TestView and TmpView will be redrawn whenever any property of myObservable changes.

---

<div class="post-metadata">

**Author:** ![Jon\_Shier](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jon_shier/32/921_2.png) [@Jon\_Shier](https://forums.swift.org/u/Jon_Shier)\
**Post date:** [February 15, 2025, 3:02am UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876/7 "2025-02-15T03:02:44Z")

</div>

> [@mattcurtis](#):
>
> SwiftUI records accesses to the properties of Observable objects during evaluations of `body`, and _only_ re-renders that view if a property it used actually changes.

Unfortunately I don't think this is quite correct. Observation is triggered if an accessed property is _set_, not if it _changes_. You can see this by setting a property equal to itself. `body` should be recomputed, though there may be no rendering update depending on SwiftUI's implementation details. That `Observable` (or SwiftUI) doesn't require `Equatable` conformance in the state should also reiterate this fact. I did see some PRs in the Swift repo to enhance `Observable` to do observation-on-change, but I don't know if that's actually going to happen.

---

<div class="post-metadata">

**Author:** ![mattcurtis](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mattcurtis/32/18493_2.png) [@mattcurtis](https://forums.swift.org/u/mattcurtis)\
**Post date:** [February 15, 2025, 1:11pm UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876/8 "2025-02-15T13:11:27Z")

</div>

> [@Jon\_Shier](#):
>
> Observation is triggered if an accessed property is _set_, not if it _changes_.

That's an important distinction — you're right, SwiftUI isn't tracking equality.

---

<div class="post-metadata">

**Author:** ![jflan](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jflan/32/15474_2.png) [@jflan](https://forums.swift.org/u/jflan)\
**Post date:** [February 15, 2025, 2:11pm UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876/9 "2025-02-15T14:11:10Z")

</div>

Checking if equatable values changed was added to the `set` accessor after the release of 6.0. Hopefully it’ll be in 6.1.

> <https://github.com/swiftlang/swift/blob/afb4d1397df6fbd6d8b90ab8c7f59fa535c04133/lib/Macros/Sources/ObservationMacros/ObservableMacro.swift#L367>

---

<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:** [February 16, 2025, 9:36pm UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876/10 "2025-02-16T21:36:28Z")

</div>

Hmm… I didn't see much discussion about the `shouldNotifyObservers` pattern before this landed. Was there any public evolution pitch about this?

> <https://github.com/swiftlang/swift/pull/78151>
>
> This alters the macro code-generation for Observable types. The types now house …a distinctness check by testing \`shouldNotifyObservers\` which by default emits only on new values for things that conform to equatable.
> 
> Furthermore it removes the bottleneck of creating key paths by pre-caching them as \_cachedKeypath\_ prefixed static members. This avoids the cache miss potential of KeyPaths and also avoids the cache lookup/lock acquisition (which dominate most of the traces of high frequency accessed properties for observable types).
> 
> Additionally some table bookkeeping was removed so that the registrar itself is now faster.

---

<div class="post-metadata">

**Author:** ![mattcurtis](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mattcurtis/32/18493_2.png) [@mattcurtis](https://forums.swift.org/u/mattcurtis)\
**Post date:** [February 17, 2025, 6:04pm UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876/11 "2025-02-17T18:04:33Z")

</div>

(@vanvoorden your [comment on that pull request](https://github.com/swiftlang/swift/pull/78151#issuecomment-2662315769) was very thorough and a great catch — according to what you noticed these changes _would_ result in unexpected behavior.)

---

<div class="post-metadata">

**Author:** ![Philippe\_Hausler](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/philippe_hausler/32/22569_2.png) [@Philippe\_Hausler](https://forums.swift.org/u/Philippe_Hausler)\
**Post date:** [February 17, 2025, 6:26pm UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876/12 "2025-02-17T18:26:21Z")

</div>

The addition of the `shouldNotifiyObservers` was landed as an optimization to the code-generation; it is not intended to be API for external (outside of the type) calls. Those types of things don't usually go through evolution; however if it is causing problems we should figure out how to resolve it since that can be (in some scenarios a very good benefit to SwiftUI's performance).

---

<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:** [February 17, 2025, 6:51pm UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876/13 "2025-02-17T18:51:58Z")

</div>

> [@Philippe\_Hausler](#):
>
> The addition of the `shouldNotifiyObservers` was landed as an optimization to the code-generation; it is not intended to be API for external (outside of the type) calls.

Respectfully (please assume good intent when I suggest this)… if [`withObservationTracking(_:onChange:)`](https://developer.apple.com/documentation/observation/withobservationtracking(_:onchange:)) is shipping as a public API… and `shouldNotifyObservers` alters the behavior of that API in non-trivial ways (other than fixing something we clearly understood to be a bug)… would that not be a very good candidate for opening a pitch and proposal evolution review for more input from the community before we ship to `main`?

---

<div class="post-metadata">

**Author:** ![bzamayo](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/bzamayo/32/764_2.png) [@bzamayo](https://forums.swift.org/u/bzamayo)\
**Post date:** [February 17, 2025, 7:02pm UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876/14 "2025-02-17T19:02:11Z")

</div>

I think your counter-example defies the strict `Equatable` contract though. [From the docs](https://developer.apple.com/documentation/swift/equatable),

`Equality implies substitutability—any two instances that compare equally can be used interchangeably in any code that depends on their values. `

If that was upheld by your custom type, the changes to observation tracking would have no observable effect on client code. So, I _think_ with that in mind, the changes have no undocumented changes to observable behaviour.

---

<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:** [February 17, 2025, 7:15pm UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876/15 "2025-02-17T19:15:28Z")

</div>

> [@bzamayo](#):
>
> If that was upheld by your custom type, the changes to observation tracking would have no observable effect on client code. So, I _think_ with that in mind, the changes have no undocumented changes to observable behaviour.

Expanding on that idea… here is what the documentation for `Equatable` has to say about _identity_:\[1\]

> ## Equality is Separate From Identity
> 
> The identity of a class instance is not part of an instance’s value.

The implication here could be that `Equatable` does imply two instances are interchange WRT code that depends on their _values_… but code that depends on their _identity_ (assuming we are talking about reference types) should be able to continue its orthogonal tests for equality by identity.

* * *

1. [Equatable | Apple Developer Documentation](https://developer.apple.com/documentation/swift/equatable#Equality-is-Separate-From-Identity)

---

<div class="post-metadata">

**Author:** ![Jon\_Shier](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jon_shier/32/921_2.png) [@Jon\_Shier](https://forums.swift.org/u/Jon_Shier)\
**Post date:** [February 17, 2025, 7:17pm UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876/16 "2025-02-17T19:17:48Z")

</div>

While true, this change will also break uses of `state = state` used to trigger additional, if redundant, observations, sometimes used to work around SwiftUI or other reactive bugs. Personally I don't think that use case is important enough to warrant blocking this much needed improvement, but there may still be an impact due to outside factors the developer can't control.

---

<div class="post-metadata">

**Author:** ![Jon\_Shier](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jon_shier/32/921_2.png) [@Jon\_Shier](https://forums.swift.org/u/Jon_Shier)\
**Post date:** [February 17, 2025, 7:28pm UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876/17 "2025-02-17T19:28:43Z")

</div>

While I agree with @bzamayo that your `Equatable` implementation is incorrect (if you want to consider identity as part of equality, you need to _always_ consider it), I do agree it's strange that this sort of optimization was implemented by preventing the _set_ rather than the emission of the observation. This goes against every expectation I would have (`@Observable` should not change the externally visible behavior of types except by adding observation) and seems like it would break the local `didSet` observer. @Philippe_Hausler was there a reason for this approach rather than allowing the set and the optimizing the emission of observation itself?

---

<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:** [February 17, 2025, 7:41pm UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876/18 "2025-02-17T19:41:54Z")

</div>

> [@Jon\_Shier](#):
>
> your `Equatable` implementation is incorrect (if you want to consider identity as part of equality, you need to _always_ consider it)

This is in reference to the check for identity equality in the check for value equality?

```swift
extension Car: Equatable {
  static func == (lhs: Car, rhs: Car) -> Bool {
    if lhs === rhs {
      return true
    }
    guard
      lhs.name == rhs.name
    else {
      return false
    }
    guard
      lhs.needsRepairs == rhs.needsRepairs
    else {
      return false
    }
    return true
  }
}

```

If two `Car` instances are equal by identity they must be equal by value. If two `Car` instances are not equal by identity we confirm value equlity from the stored instance properties. Could you please help me find the faulty logic?

---

<div class="post-metadata">

**Author:** ![mattcurtis](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mattcurtis/32/18493_2.png) [@mattcurtis](https://forums.swift.org/u/mattcurtis)\
**Post date:** [February 17, 2025, 7:52pm UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876/19 "2025-02-17T19:52:33Z")

</div>

> [@Jon\_Shier](#):
>
> I do agree it's strange that this sort of optimization was implemented by preventing the _set_ rather than the emission of the observation.

This seems like a key part: I think users (myself at the top of that list) would appreciate and want optimizations like this that make prevent views from updating needlessly. What feels out of place is that it changes the intuitive behavior of setters: values users would expect to be held _won't_ be, on types and properties they've defined themselves.

---

<div class="post-metadata">

**Author:** ![Jon\_Shier](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jon_shier/32/921_2.png) [@Jon\_Shier](https://forums.swift.org/u/Jon_Shier)\
**Post date:** [February 17, 2025, 7:53pm UTC](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876/20 "2025-02-17T19:53:41Z")

</div>

Yes, you return `true` if the identity is the same, but don't return `false` if they're not. Technically I think it violates the substitution principle, which generally means the properties of equal things should also be equal, including their inequality, but I'm not enough of a mathematician to be sure.

[Next page](https://forums.swift.org/t/understanding-when-swiftui-re-renders-an-observable/77876.md?page=2)
