SwiftUI State Management Fundamentals
Tags: swiftui, swift, ios, mobile
Published: 2023-03-29 10:20:10

This article is an attempt to help beginners, like myself, understand fundamental things about SwiftUI. This is the type of article I wish existed when I was just starting. Hope it’ll save you some time and struggle.

Feel free to leave a comment with corrections or general feedback.

---

## Prerequisites
In order to really understand state management in SwiftUI, and in order to follow along with this article, you need to know a couple of things:

- Difference between value type and reference type
- What property wrappers are and roughly how they work

If you don’t feel confident enough about those topics, please take a moment to read these documents:

- [Structures and Classes](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/classesandstructures/)
- [Property Wrappers](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/properties#Property-Wrappers)

Otherwise, let’s dive in.

---

## Application Pipeline

On a very high level, every app is a pipeline that takes some event as an input (user gesture, system notification, timer, etc.) and produces an output, most often by drawing something on the screen.

Different frameworks have different intermediate steps between input and output. In SwiftUI, the pipeline looks roughly like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zrim0r7mubdvz90wz9nr.png)

---

## Ephemeral Views

Take a look at the _Re-create Views_ step in the pipeline. By Views, I mean our custom `structs` with the `body` property, just to be clear.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bslmosvdnafebgqi9vx1.png)

It’s one of the crucial points when it comes to understanding the application state in SwiftUI. A lot of other concepts are direct consequences of this fact:

**Views are disposed of and re-created every time an event occurs and the observed state changes.**

This means we cannot reliably keep any data within our views. We need some other (external) place to store the application state.

Luckily, SwiftUI has built-in storage outside of the view hierarchy which we can use: SwiftUI State. This state is managed entirely by the framework, and we access it only through the provided API.

---

## Triggering View Re-creation

From the pipeline, you can also see that the only way to make SwiftUI re-create our views and, eventually, to re-render the screen is to update the observed data.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3q90c44jogy5opz1j37c.png)

This means that we, somehow, need to tell SwiftUI to look for changes in some properties of our view and when they occur — re-create the view.

---

## @State

So, to recap:
- We need a way to put data in SwiftUI state which lives outside of our views
- We need to tell SwiftUI to observe changes in some properties, so the framework knows when to re-create views and re-render

`@State` property wrapper solves both problems at once. Let’s look at the simple app and its state layout.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r2t5959rbzm4yhn4xprf.png)

`@State` creates an observable container for our property and puts it outside of the view. SwiftUI now keeps the connection between our view and the observed data. So that next time this view is re-created, it can inject the data into the corresponding property of the view.

## Demo

{% embed https://www.youtube.com/watch?v=QfLI_Xh8GeI %}
[Source code of the example](https://gist.github.com/mykolaharmash/06fe28006cfa1588c41b19e386846bf2)

Notice that `Counter` view has `init()` with `print()` inside.

Every time the _Random_ button is pressed you see a message in the XCode console, meaning the `Counter` view was re-created, but the `count` keeps the same number because it’s stored outside of the view and injected every time the view is created.

---

## View Re-creation vs. View Disappearance

It’s worth mentioning the distinction between SwiftUI disposing of an old version of a view before creating a new one and your code intentionally excluding a view from rendering.

In the latter case, SwiftUI will notice that the view is not a part of the rendered screen anymore and will release all the values it kept for this view in the state. The next time your code decides to show the view again, its properties will be put back in the state with their initial values.

Here is a demo of this effect.

{% embed https://www.youtube.com/watch?v=Rgtu1GAtZbM %}
[Source code of the example](https://gist.github.com/mykolaharmash/bc2608096cc48a6e0b418a0178a89151)

---

> **shameless plug**
> I try to regularly post useful dev content on [my Twitter](https://twitter.com/mykolaharmash), feel free to follow me there 😉

---


## @State With Reference Types

A common misconception is that you cannot use `@State` with reference types like classes. You definitely can, there is no technical limitation to do it.

You just need to keep in mind that the property you wrap with `@State` will hold a reference, and a reference will be the only thing SwiftUI will store in the state and observe for changes.

Remember that if you update a property of a reference type, its reference does not change. So to SwiftUI, it will look like nothing happened.

But `@State` does not care what to wrap with an observable container and put in the state, it’ll do it just the same way it does for value types.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zp8nvlm0dmaow5gbfon9.png)

If you try to run this code you’ll see that it does not work.

{% embed https://www.youtube.com/watch?v=HWuFBD2sPTk %}
[Source code of the example](https://gist.github.com/mykolaharmash/a47b109bbdbdc613693dcc1673a6cc08)

Here, I’m updating the `count` property by using `increment()` and it updates as it should. The only thing is that reference that lives in SwiftUI state does not ever change, so the view is never re-created, and the screen is never updated.

Logically, to fix this example we need to update the reference every time we hit the button.

{% embed https://www.youtube.com/watch?v=jOw1lG35xrk %}
[Source code of the example](https://gist.github.com/mykolaharmash/a9929ed3e9704db66c18b76d4945f14f)

This works, but in most cases, it’s not quite the solution we really want. Re-creating an instance every time might be wasteful in a lot of cases, not to mention that code written this way does not communicate the intent really well. We want to increment the count on a counter, not a brand new counter every time.

---

## ObservableObject, @ObservedObject and @Published

These three play together to give us a way to change individual properties of a reference type and still communicate changes to SwiftUI, so it knows to re-render.

`ObservableObject` is a protocol you adopt on your reference type and `@ObservedObject` is a property wrapper for a view property, which holds a reference.

By using `@ObservedObject` on a property, you are basically saying to SwiftUI to go and look inside the object that the property holds a reference to. Look inside and subscribe to changes from properties that are marked with `@Published`.

`@Published` wraps a property on a reference type with the observable container, just like `@State` does it for view properties.

This is how it looks in the code of the Counter app.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6xisn9lijllk53vimloa.png)
[Source code of the example](https://gist.github.com/mykolaharmash/f91d636a2079b443b5f788f12f9009eb)

Now it works with just calling `increment()`, without re-creating the counter every time.

Unfortunately, this approach has a flaw and you can see it in the image above. SwiftUI State is empty. `@ObservedObject` does not put data outside of the view as `@State` does. And in case an object is created within a view, every time the view is re-created the object will be re-created as well.

Here is a demo of this effect.

{% embed https://www.youtube.com/watch?v=jnRKpbiaKGI %}
[Source code of the example](https://gist.github.com/mykolaharmash/915f9252d655b860f040a36983a3d8d1)

Notice how the counter at the top resets every time the Random button is pressed. When we update the `random` property, SwiftUI executes the body of the `ContentView` and re-creates `CounterView` along the way. The newly created `CounterView` ends up with a fresh instance of the `Counter` every single time.

---

## @StateObject

This is where `@StateObject` can help us out. It does all the same things as `@ObservedObject` but additionally puts observed properties into the state outside of the view hierarchy.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o7jr8s47igcf2hayoxs2.png)

Below, watch a demo with the _Random_ button, where it uses the `@StateObject` on the `counter` property this time.

{% embed https://www.youtube.com/watch?v=8MBExUZk2wQ %}

---

## When to use @ObservedObject instead of @StateObject?

It might seem that we just need to use `@StateObject` all the time and forget about `@ObservedObject`. But there is a very good use case when you need both.

Use `@StateObject` in the view which creates an object. Use `@ObservedObject` when the view receives objects from outside.

Consider this example:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bbswcftf2bmt8zl8edjj.png)
[Source code of the example](https://gist.github.com/mykolaharmash/87e4733423b08e787ee797b6a5570999)

Notice that even though two views reference and observe at the same `Counter` instance, SwiftUI state keeps only one value for the `count` property. This is because we’ve used `@ObservedObject` on the `CounterView`, which does not put anything into the state.

---

Thank you for reading, I hope it was helpful! 🙌

[My Twitter](https://twitter.com/mykolaharmash) in case you'd like to reach out.