What Does the Swiftui '@State' Keyword Do

What does the SwiftUI `@State` keyword do?

The @State keyword is a @propertyWrapper, a feature just recently introduced in Swift 5.1. As explained in the corresponding proposal, it's sort of a value wrapper avoiding boilerplate code.


Sidenote: @propertyWrapper has previously been called @propertyDelegate, but that has changed since. See this post for more information.


The official @State documentation has the following to say:

SwiftUI manages the storage of any property you declare as a state.
When the state value changes, the view invalidates its appearance and
recomputes the body
. Use the state as the single source of truth for a
given view.

A State instance isn’t the value itself; it’s a means of
reading and mutating the value
. To access a state’s underlying value,
use its value property.

So when you initialize a property that's marked @State, you're not actually creating your own variable, but rather prompting SwiftUI to create "something" in the background that stores what you set and monitors it from now on! Your @State var just acts as a delegate to access this wrapper.

Every time your @State variable is written, SwiftUI will know as it is monitoring it. It will also know whether the @State variable was read from the View's body. Using this information, it will be able to recompute any View having referenced a @State variable in its body after a change to this variable.

How SwiftUI detects there are @State variable values passed to subviews?

When you use the state property wrapper SwiftUI can know when a variable is read, because it can manage access via custom getter function.
When a view builder closure is being evaluated, SwiftUI knows which builder function it’s evaluating and can set its state such that any state variables read during that evaluation are then known to be dependencies of the view currently being evaluated.

At runtime: the flow might look like this:

Need to render ContentView, so:
set currentView = ContentView
call each function in the function builder...
...LabelView(number: counter) - this calls the getter for counter

Getter for counter is:
associate <currentView> with counter (means any time counter changes, need to recompute <currentView>)
return value of counter


...after ContentView function builder
Current view = nil

But with a stack of current views...

What is the some keyword in Swift(UI)?

some View is an opaque result type as introduced by SE-0244 and is available in Swift 5.1 with Xcode 11. You can think of this as being a "reverse" generic placeholder.

Unlike a regular generic placeholder which is satisfied by the caller:

protocol P {}
struct S1 : P {}
struct S2 : P {}

func foo<T : P>(_ x: T) {}
foo(S1()) // Caller chooses T == S1.
foo(S2()) // Caller chooses T == S2.

An opaque result type is an implicit generic placeholder satisfied by the implementation, so you can think of this:

func bar() -> some P {
return S1() // Implementation chooses S1 for the opaque result.
}

as looking like this:

func bar() -> <Output : P> Output {
return S1() // Implementation chooses Output == S1.
}

In fact, the eventual goal with this feature is to allow reverse generics in this more explicit form, which would also let you add constraints, e.g -> <T : Collection> T where T.Element == Int. See this post for more info.

The main thing to take away from this is that a function returning some P is one that returns a value of a specific single concrete type that conforms to P. Attempting to return different conforming types within the function yields a compiler error:

// error: Function declares an opaque return type, but the return
// statements in its body do not have matching underlying types.
func bar(_ x: Int) -> some P {
if x > 10 {
return S1()
} else {
return S2()
}
}

As the implicit generic placeholder cannot be satisfied by multiple types.

This is in contrast to a function returning P, which can be used to represent both S1 and S2 because it represents an arbitrary P conforming value:

func baz(_ x: Int) -> P {
if x > 10 {
return S1()
} else {
return S2()
}
}

Okay, so what benefits do opaque result types -> some P have over protocol return types -> P?


1. Opaque result types can be used with PATs

A major current limitation of protocols is that PATs (protocols with associated types) cannot be used as actual types. Although this is a restriction that will likely be lifted in a future version of the language, because opaque result types are effectively just generic placeholders, they can be used with PATs today.

This means you can do things like:

func giveMeACollection() -> some Collection {
return [1, 2, 3]
}

let collection = giveMeACollection()
print(collection.count) // 3

2. Opaque result types have identity

Because opaque result types enforce a single concrete type is returned, the compiler knows that two calls to the same function must return two values of the same type.

This means you can do things like:

//   foo() -> <Output : Equatable> Output {
func foo() -> some Equatable {
return 5 // The opaque result type is inferred to be Int.
}

let x = foo()
let y = foo()
print(x == y) // Legal both x and y have the return type of foo.

This is legal because the compiler knows that both x and y have the same concrete type. This is an important requirement for ==, where both parameters of type Self.

protocol Equatable {
static func == (lhs: Self, rhs: Self) -> Bool
}

This means that it expects two values that are both the same type as the concrete conforming type. Even if Equatable were usable as a type, you wouldn't be able to compare two arbitrary Equatable conforming values with each other, for example:

func foo(_ x: Int) -> Equatable { // Assume this is legal.
if x > 10 {
return 0
} else {
return "hello world"
}
}

let x = foo(20)
let y = foo(5)
print(x == y) // Illegal.

As the compiler cannot prove that two arbitrary Equatable values have the same underlying concrete type.

In a similar manner, if we introduced another opaque type returning function:

//   foo() -> <Output1 : Equatable> Output1 {
func foo() -> some Equatable {
return 5 // The opaque result type is inferred to be Int.
}

// bar() -> <Output2 : Equatable> Output2 {
func bar() -> some Equatable {
return "" // The opaque result type is inferred to be String.
}

let x = foo()
let y = bar()
print(x == y) // Illegal, the return type of foo != return type of bar.

The example becomes illegal because although both foo and bar return some Equatable, their "reverse" generic placeholders Output1 and Output2 could be satisfied by different types.


3. Opaque result types compose with generic placeholders

Unlike regular protocol-typed values, opaque result types compose well with regular generic placeholders, for example:

protocol P {
var i: Int { get }
}
struct S : P {
var i: Int
}

func makeP() -> some P { // Opaque result type inferred to be S.
return S(i: .random(in: 0 ..< 10))
}

func bar<T : P>(_ x: T, _ y: T) -> T {
return x.i < y.i ? x : y
}

let p1 = makeP()
let p2 = makeP()
print(bar(p1, p2)) // Legal, T is inferred to be the return type of makeP.

This wouldn't have worked if makeP had just returned P, as two P values may have different underlying concrete types, for example:

struct T : P {
var i: Int
}

func makeP() -> P {
if .random() { // 50:50 chance of picking each branch.
return S(i: 0)
} else {
return T(i: 1)
}
}

let p1 = makeP()
let p2 = makeP()
print(bar(p1, p2)) // Illegal.

Why use an opaque result type over the concrete type?

At this point you may be thinking to yourself, why not just write the code as:

func makeP() -> S {
return S(i: 0)
}

Well, the use of an opaque result type allows you to make the type S an implementation detail by exposing only the interface provided by P, giving you flexibility of changing the concrete type later down the line without breaking any code that depends on the function.

For example, you could replace:

func makeP() -> some P {
return S(i: 0)
}

with:

func makeP() -> some P { 
return T(i: 1)
}

without breaking any code that calls makeP().

See the Opaque Types section of the language guide and the Swift evolution proposal for further information on this feature.

SwiftUI – @State vs @Binding

SwiftUI is a declarative Component-Oriented framework. You have to forget about MVC where you have controllers mediating between view and model. SwiftUI uses diffing algorithm to understand changes and update only corresponding views.

@State

  • A State property is connected to the view. A State property is permanently being read by the view. That means that every time the @State property gets changed/updated, the view gets re-rendered and eventually displays the content depending on the @State's data.
  • State is accessible only to a particular view.
  • Simple properties like strings, integers and booleans belongs to a single view - mark as private.
  • All the fields marked as State are stored in special separated memory, where only corresponded view can access and update them.

@Binding

  • BindableObject protocol, which requires a didChange property. It makes possible to use it inside Environment and rebuild view as soon as it changes.
  • The didChange property should be a Publisher, which is a part of a new Apple’s Reactive framework called Combine.
  • The main goal of Publisher is to notify all subscribers when something changes. As soon as new values appear, SwiftUI will rebuild Views.

@EnvironmentObject

  • It is a part of feature called Environment. You can populate your Environment with all needed service classes and then access them from any view inside that Environment.
  • @EnvironmentObject is accessible for every view inside the Environment.
  • @EnvironmentObject Properties created elsewhere such as shared data. App crashes if it is missing.
  • The Environment is the right way of Dependency Injection with SwiftUI.

What does the dollar sign do in Swift / SwiftUI?

The $ is used in conjunction with property delegates.

It's not an operator, but a prefix (thanks @matt!).

For more about property delegates, see this Swift Evolution document.

e.g. in @State var aState = false, State is a property delegate.

This means that if we write:

  • aState we're accessing a Bool value
  • $aState we're accessing a Binding<Bool> value

Different property delegates will generate different values.

Difference between @propertyDelegate and @propertyWrapper

TLDR; they are the same but you should use @propertyWrapper.

As pointed out by @JosefDolezal on twitter https://twitter.com/josefdolezal/status/1137619597002248192?s=21, the name @propertyDelegate was returned for revision by the core team https://forums.swift.org/t/returned-for-revision-se-0258-property-delegates/24080. The core team proposed multiple alternative namings, but because the proposal wasnt finalized before WWDC, they chose one of them to introduce this feature to the world.

So its very likely that @propertyDelegate will be removed, and likely that @propertyWrapper will stay, although this could still change during the ongoing evolution process.

SwiftUI @State Variables

You don't need to declare TotalFrequency as a State variable cause both the mhzValue and mhzValueStep are declared as State var and you just need the sum of this two.
Rather you can declare TotalFrequency as a computed var. Here is a working version of the code.

import SwiftUI
struct ContentView : View {

@State var mhzValue : Float = 0
@State var mhzValueStep : Float = 0
private var TotalFrequency : Float {
return mhzValue + mhzValueStep
}

var body: some View {
VStack {
//Slider one
Text("Slide to select Frequency")
.font(.headline)
.color(.blue)
.padding(.leading, -130.0)
Slider(value: $mhzValue, from: 1, through: 55, by: 1)
.padding(.horizontal)

Text("\(Int(mhzValue)) in Mhz")
.font(.title)
.fontWeight(.semibold)
.color(.blue)
// Slider Two
Text("Slide to select Decimal Point")
.font(.headline)
.color(.orange)
.padding(.leading, -130.0)

Slider(value: $mhzValueStep, from: 0, through: 1, by: 0.1)
.padding(.horizontal)
Text("\(mhzValueStep) in Mhz")
.font(.title)
.fontWeight(.semibold)
.color(.orange)

Text(" Frequency: \(TotalFrequency) Mhz")
.font(.largeTitle)
.fontWeight(.medium)
.color(.white)
.padding(10)
.background(/*@START_MENU_TOKEN@*/Color.blue/*@END_MENU_TOKEN@*/)
.cornerRadius(10.0)
.shadow(radius: /*@START_MENU_TOKEN@*/10/*@END_MENU_TOKEN@*/)

// Load Image View
Spacer()
// Image()
// .padding(.bottom, 40)

}
}
}

Can I use actors in Swift to always call a function on the main thread?

Actors in Swift 5.5 ‍♀️

Actor isolation and re-entrancy are now implemented in the Swift stdlib. So, Apple recommends using the model for concurrent logic with many new concurrency features to avoid data races. Instead of lock-based synchronisation (lots of boilerplate), we now have a much cleaner alternative.

Some UIKit classes, including UIViewController and UILabel, now have out of the box support for @MainActor. So we only need to use the annotation in custom UI-related classes. For example, in the code above, myImageView.image would automatically be dispatched on the main queue. However, the UIImage.init(named:) call is not automatically dispatched on the main thread outside of a view controller.

In the general case, @MainActor is useful for concurrent access to UI-related state, and is the easiest to do even though we can manually dispatch too. I've outlined potential solutions below:

Solution 1

The simplest possible. This attribute could be useful in UI-Related classes. Apple have made the process much cleaner using the @MainActor method annotation:

@MainActor func setImage(thumbnailName: String) {
myImageView.image = UIImage(image: thumbnailName)
}

This code is equivalent to wrapping in DispatchQueue.main.async, but the call site is now:

await setImage(thumbnailName: "thumbnail")

Solution 2

If you have Custom UI-related classes, we can consider applying @MainActor to the type itself. This ensures that all methods and properties are dispatched on the main DispatchQueue.

We can then manually opt out from the main thread using the nonisolated keyword for non-UI logic.

@MainActor class ListViewModel: ObservableObject {
func onButtonTap(...) { ... }

nonisolated func fetchLatestAndDisplay() async { ... }
}

We don't need to specify await explicitly when we call onButtonTap within an actor.

Solution 3 (Works for blocks, as well as functions)

We can also call functions on the main thread outside an actor with:

func onButtonTap(...) async {
await MainActor.run {
....
}
}

Inside a different actor:

func onButtonTap(...) {
await MainActor.run {
....
}
}

If we want to return from within a MainActor.run, simply specify that in the signature:

func onButtonTap(...) async -> Int {
let result = await MainActor.run { () -> Int in
return 3012
}
return result
}

This solution is slightly less cleaner than the above two solutions which are most suited for wrapping an entire function on the MainActor. However, actor.run also allows for inter threaded code between actors in one func (thx @Bill for the suggestion).

Solution 4 (Block solution that works within non-async functions)

An alternative way to schedule a block on the @MainActor to Solution 3:

func onButtonTap(...) {
Task { @MainActor in
....
}
}

The advantage here over Solution 3 is that the enclosing func doesn't need to be marked as async. Do note however that this dispatches the block later rather than immediately as in Solution 3.

Summary

Actors make Swift code safer, cleaner and easier to write. Don't overuse them, but dispatching UI code to the main thread is a great use case. Note that since the feature is still in beta, the framework may change/improve further in the future.

Since we can easily use the actor keyword interchangeably with class or struct, I want to advise limiting the keyword only to instances where concurrency is strictly needed. Using the keyword adds extra overhead to instance creation and so doesn't make sense when there is no shared state to manage.

If you don't need a shared state, then don't create it unnecessarily. struct instance creation is so lightweight that it's better to create a new instance most of the time. e.g. SwiftUI.



Related Topics



Leave a reply



Submit