removeAll is triggering an observation even though array is unchanged

Recently I have noticed an interesting thing when using the Observable annotation. I have a model marked with Observable, and there is an array in the model, at some point I call the removeAll on this array using a where condition, and even though the condition isnt matching, and nothing is removed from the array, I noticed the observation is being triggered, and swiftui thinks the array is changed, but in reality the array is unchanged. Why is this happening ?

struct ObservableArrayTestView: View {
    
    @State var model = AppModel()
    
    var body: some View {
        let _ = Self._printChanges()
        Button {
            model.removeCard()
        } label: {
            Text(model.title)
        }
    }
}

and model:

@MainActor @Observable
class AppModel {
    
    var cards: [String] = []
    
    var title: String {
        cards.isEmpty ? "" : "Cards (\(cards.count))"
    }
    
    init() {
        cards = (1...20).map { "Card \($0)" }
    }
    
    func removeCard() {
        cards.removeAll(where: { $0 == "test" })
    }
}

here is what I see when button is tapped:

ObservableArrayTestView: \AppModel.cards changed.
ObservableArrayTestView: \AppModel.cards changed.
ObservableArrayTestView: \AppModel.cards changed.
ObservableArrayTestView: \AppModel.cards changed.

how can we avoid from this observation to happen ? I understand, we can add a guard check that only call removeAll if it contains in the array, and this observation wouldn't happen.

Why is removeAll changing the array ? I even checked the array memory address, it is same, so how is this observation change coming ?

I think the problem has to do with the init in AppModel and initializing model outside of body. Does the behavior change when you initialize model inside an onAppear or task modifier?

The @State documentation states the following for observable objects:

A State property always instantiates its default value when SwiftUI instantiates the view. For this reason, avoid side effects and performance-intensive work when initializing the default value. For example, if a view updates frequently, allocating a new default object each time the view initializes can become expensive. Instead, you can defer the creation of the object using the task(priority:_:) modifier, which is called only once when the view first appears

Delaying the creation of the observable state object ensures that unnecessary allocations of the object doesn’t happen each time SwiftUI initializes the view. Using the task(priority:_:) modifier is also an effective way to defer any other kind of work required to create the initial state of the view, such as network calls or file access.

Also see this post: @Observable init() called multiple times by @State, different behavior to @StateObject

What version of Xcode / Swift are you using?

As suggested, you may want to double check if AppModel.init is being called multiple times, but depending on your version of Swift, this may just be a product of @Observable and value types, where observers are called whenever the value is set, such as when called in a mutating method, like removeAll(). Swift 6.2 is supposed to help a bit there by deduplicated observations if values don't change, but it's not consistent IME.