- Swift triggers observable updates even if
removeAll()does not change an array’s state. @Observablewatches for the intent to change, not actual data changes.- Calls to
_modifyalways trigger reactivity, making sure views stay current. - Even calls that do nothing (like sorting an already-sorted list) make SwiftUI update views.
- Checking data before making changes can reduce unneeded view updates.
How Swift removeAll Acts and @Observable Triggers
You are making a SwiftUI app. You decide to clean up by calling array.removeAll() on a property linked to data. The array is already empty. But then you see your view still updates. And a related Combine publisher also acts. This might surprise you. However, in Swift, the plan to change something is as important as a real change. We will look at Swift removeAll, Swift Observable, and how @Observable works. This will show why these updates happen, even when they seem unneeded.
How Swift’s @Observable and Change Detection Work
When you build things in SwiftUI or Combine, developers use property wrappers like @Observable, @Published, or @State. These help make the UI match changes in data. Such wrappers make Swift objects respond to changes. This means when a property that is watched seems to change, other parts are told, and the UI updates. But Swift's system for changes does not wait to see exact data differences. Instead, it finds and reacts to the plan to change, not the final result.
Why Being Careful with Changes Can Have a Cost
Swift’s system for watching changes is careful and puts safety first. It thinks a change might have happened when certain parts of code are used. This happens even if the value stays the same after the code runs. This is key to keeping your UI right and not missing real changes. But, this also means Swift sometimes makes updates happen even when nothing really changed.
This behavior also happens a lot with list operations, like removeAll(). A change is suggested, even if the array was empty before.
Understanding removeAll() on Empty Arrays in Swift
removeAll() is a method that changes things. It works based on Swift’s rules for change. It does not check the result in context. It changes the array by taking out all its items. Still, if the array is already empty, the items stay the same both before and after the method call. So, why does Swift—and also because of this, @Observable—see this method call as a change?
This is how Swift’s change tracking works: any function call that changes something calls internal parts that guess a change might happen. This basic way of working is important. It helps Swift with copy-on-write (CoW), keeping values safe, and making the UI update quickly.
What the _modify Accessor Does in Swift
To fully grasp how Swift tracks changes, you need to know about the _modify accessor. This is a low-level part built into how Swift gets and sets properties.
What is _modify?
The _modify accessor is an internal tool made by the compiler. It lets you read and write a property. It does not copy its contents right away. This makes sure memory is used well. And it lets you change things right where they are, following Swift’s copy-on-write rules.
When a property is set to be changeable (with var) and you do an operation that changes something (like sort(), removeAll(), or changing an item), Swift does not just say the property changed. It also sends the access through _modify.
struct MyModel {
var array: [Int]
mutating func reset() {
array.removeAll() // internally uses _modify
}
}
This helps performance. But it also has another effect: the _modify accessor signals that the property could be changed. Property wrappers like @Observable see this as a sign of change, even if the value stays the same after the code runs.
Why Swift Cannot Know Ahead of Time
The Swift compiler and runtime cannot know the exact result of a change ahead of time. Many operations rely on data at the time the code runs, for example, what an array holds. Trying to find out if a change truly happened early would mean checking and comparing values before and after every action. This would add a lot of extra work.
So, Swift takes a careful path: if a change could happen, tell other parts.
What Triggers from Observable and the Effects of _modify
When you use @Observable (or @Published in Combine), any time you write to a property—even using _modify—it makes a change notification happen.
This includes calls when:
- The array is still empty after
removeAll(). - An array that was reversed ends up with the same items in the same order.
- Sorting a list that is already sorted makes no difference in order.
- Joining an empty array adds no new items.
@Observable
class ViewModel {
var list: [String] = []
func clear() {
list.removeAll() // Notification sent even if list == []
}
}
No “real” data change happened. But Swift sees the _modify trigger. It then sends a change notice to everything watching list.
This is a key part of how it works, not a mistake.
Real Problems with Unneeded SwiftUI View Updates
In SwiftUI, views respond to state changes. Each time a watched item signals a change, SwiftUI makes a view update. For small apps, this might not matter. But when an app gets bigger, extra view updates can bring:
- ⚠️ UI slows down
- ✨ Animations start over at odd times
- 🌀 Layout takes longer to figure out
- 🧭 Scroll position acts weird
- 📉 Update loops use CPU for no good reason
A change signal from removeAll() on an already empty array might not show up alone. But if you have dozens or hundreds of them in your app's system for changes, you could see things slow down.
SwiftUI compares how views are
In SwiftUI’s way of showing things, views use body diffing and how views are to work. When a change is marked, SwiftUI checks if the body has changed. If views use properties that are watched and used in changes, even a call that does nothing can rebuild a whole set of views.
This is very important for lists, screen changes, or any views that use modifier state (for example, .opacity, .animation()).
Tips for Performance and Debugging
To make sure few things trigger changes while still keeping things quick, developers should be mindful of changes.
🔍 Tips to Help:
-
✅ Do not make changes without checking:
if !items.isEmpty { items.removeAll() } -
🧭 Test watchers with logs:
Use Combine.print()or SwiftUI debug log statements to see when publishers send out data. -
🛠 Use tools to track changes:
CustomdidSet,willSet, or even Combineassignsinks can help you see unwanted messages. -
🧱 Think about using middle models:
Break up view models into parts just for UI and parts just for data. This keeps the watchable parts separate. -
🧘 Use debounce and delay tools:
When changes happen one after another, use Combine’s.debounce(for:scheduler:)to delay updates. This makes fewer redraws happen.
Where This Behavior Shows Up
Knowing when Swift removeAll could wrongly get blamed for bugs helps stop wrong fixes:
1. View Flickers On Refresh
You call removeAll() inside .onAppear. Every time a sheet shows, a List rebuilds. This makes the user see a flicker or lose where they were scrolled.
2. Animations Start Again
You use .animation(.easeIn) in a view that uses watchable items. Then you reset the state without a real change. The animation starts again because SwiftUI sees a watched item changed.
3. State Loops Inside Combine Pipelines
Even array changes that do nothing restart Combine publishers. This could lead to calls that loop or start debounce logic that you did not mean to.
Ways to Act Early: Making Fewer Unneeded Observable Triggers
Here’s how experienced SwiftUI developers keep change watching simple and useful:
1. Use Setters with Conditions
Putting changes inside guard statements stops them from signaling updates:
func clearItemsIfNeeded() {
guard !items.isEmpty else { return }
items.removeAll()
}
2. Checks for Being the Same
Sometimes this can help you:
let newList: [String] = []
if newList != items {
items = newList
}
This adds work to compare things. But it makes fewer unneeded UI triggers happen.
3. Storage That Does Not Watch for Quiet State
Not every way of storing data needs to be inside an @Observable object. Quiet state (used only inside) can avoid forced view updates by staying outside the change-watching system.
You might write it like this:
class CacheContainer {
var historyBuffer: [String] = []
}
Outside of the change-watching flow, this state can change quietly without changing SwiftUI.
Other Ways to Use removeAll() with Better Control
If you are setting state directly to a new value, think about setting the property directly instead of removeAll():
// Safer – but still observable
if !items.isEmpty {
items = []
}
Setting things directly lets you put logic in one place, like in a setter or inside a custom wrapper class. For example:
class MyModel {
var items: [String] {
didSet {
// Compare old and new if needed
}
}
}
This lets you decide more carefully about when to tell others or deal with updates.
In ways to build apps like MVVM or Redux-style state stores, this control becomes even more important to protect how well it works and how users feel about it.
@Observable and SwiftUI: Last Thoughts
Knowing how @Observable acts well is not just knowing when things change. It is also knowing when Swift thinks they could change.
Swift’s careful change system makes sure it works well on all devices and in all situations. But it can also cause problems, like unneeded view updates. If you are careful about changes—especially with methods like removeAll()—you can make things work better. You can stop flickers and make a better experience.
To sum up:
swift removeAllis always a call that changes things—even on an empty array.- The
_modifyaccessor tells Swift’s system for watching that a change might happen. Swift @Observableacts on the plan to change, not the final state.- You must handle observation triggers carefully when performance is key.
Want to learn more? Look at our Devsolus training guides. They cover Combine pipelines, managing state well in SwiftUI, and how to build strong Swift patterns with @Observable.
Sources
Apple. (2023). Swift: The Language Guide. Retrieved from https://developer.apple.com/documentation/swift
Swift Forums. (n.d.). Property wrappers and _modify. Retrieved from https://forums.swift.org
Apple. (2023). SwiftUI Essentials. Retrieved from https://developer.apple.com/documentation/swiftui