Actors
Actors
Intro
Reference type.
Single threaded.
Much better solution than relying on classes for concurrency.
New Kid on the Block
Reference type just like Class
and function
but it guarantees exclusive access to its execution environment with independent threads. Of course it does have to maintain references and the reference
type checks and all that goody / baddy stuff for updating states across all the layers whenever something changes due to it inherently being of reference
type but still great for Swift concurrency manifesto and not worrying about one more extra thing.
SwiftUI ObservableObject
conforming classes can be marked with
@MainActor
and it just works like magic.
actors | avanderlee
Swift UI
Data model - View Models with : ObservableObject
conformance to the class SwiftUIViewModel
should be annotated with @MainActor
for switching back threads from background to main.
@Publish var propertyValues
would be observed in SwiftUI Struct some View
so it is very important that UI elements data refresh things happens on Main thread.
Lot of I/O, font rasterization, 60 vs 120 frames, response times, input delay, processing relies on Main thread. UI Kit is built solely on Main Thread to run UI Tasks and make them as efficient as possible. Without any hitches or dropped frames while scrolling or doing highly intensive tasks.
how-to-use-mainactor-to-run-code-on-the-main-queue
important-do-not-use-an-actor-for-your-swiftui-data-models
Global variable as Main actor
Code snippet from the SO post linked below. Interesting things.
One way would be to store the variable within a container (like an enum
acting as an abstract namespace) and also isolating this to the main actor.
@MainActor
enum Globals {
static var foo = Foo()
}
An equally valid way would be to have a "singleton-like" static
property on the object itself, which serves the same purpose but without the additional object.
@MainActor
struct Foo {
static var shared = Foo()
}
You now access the global object via Foo.global
.
One thing to note is that this will now be lazily initialized (on the first invocation) rather than immediately initialized. You can however force an initialization early on by making any access to the object.
// somewhere early on
_ = Foo.shared
how-do-i-initialize-a-global-variable-with-mainactor
MainActor
You can always define your class as an actor which would gurantee the reference type object to be in its own context with its dedicated thread.
It is Swift's newer APIs available for users to make it more thread safe and the general guidelines are to use actor
if possible to avoid extra overhead of dealing with critical section or putting barrier or signals when writing shared / mutable data / resource.
When using Task { }
to perform any asynchronous task and utilizing the result of the async task to make some changes on UI, we need to again jump back to main thread. In order to do that sometimes SwiftUI is smart if the class is conforming to :ObservableObject
and has @mainActor
being marked it will do the thread switching in the background without explicit developer's input. But if using tasks and sending results back with completion handlers with closures. You need to be sure to preemptively switch threads to the main thread for doing UI work.
Task {
do {
let data = try await AsyncNetwork.shared.fetchData(url: url, type: User.self)
await MainActor.run {
completion(.success(data))
}
} catch { print("error") }
}
The code snippet which makes sure we are on the main thread is MainActor.run
, we can also use DispatchQueue.main.async
block. But if we are already supporting async/await and using task might as well utilize the improved API.
Actor vs MainActor
mainactor-dispatch-main-thread
Functions automatic async
If the instance class is defined as an actor
or added a override like @mainActor
then this applies it for async / await thread safety
actor CustomClass {
func doSomething() { }
}
class CustomClass2 {
@MainActor
func doSomething() { }
}
@MainActor
to annotate those functions to ensure those updates happen on the Main thread. So Swift does some magic under the hood to make that function call async, hence the await
When your View Model conforms to ObservableObject
you want to make sure your updates to Published
properties happen on the main thread.