Give your simulator superpowers

RocketSim: An Essential Developer Tool
as recommended by Apple

Swift Concurrency Course

Global actor in Swift Concurrency explained with code examples

Swift Concurrency introduced the concept of a global actor among async/await and tasks. The most common one is likely @MainActor, which I already explained in depth. However, you can also create custom global actors.

Although they have existed for a few years, it remains unclear for many developers when and how to use them. Should you even consider using them? This article will tell you all about it.

What is a Global Actor?

A global actor is precisely what it sounds like: an actor that you can apply globally. It brings the same kind of actor isolation — safe, serialized access to data — but instead of being tied to a single instance, it’s tied to something broader: like a function, a property, or even an entire type. If you’re new to actors as a whole, you might want to consider following my Swift Concurrency Course.

Here’s an example you’ve probably seen or used already:

@MainActor
func updateUI() {
    // Safely runs on the main thread
}

By marking this function with @MainActor, you’re basically saying:

“This should always run on the main thread.”

Behind the scenes, @MainActor is a global actor. It ensures that everything it touches is run on the same actor executor— in this case, the executor tied to the main thread. That makes it perfect for UI updates, which must happen on the main thread in apps.

You can also use the attribute on properties:

@MainActor
var titles: [String] = []

Whether static or not. Finally, you can use them on entire types:

@MainActor
final class ContentViewModel {
    
    var titles: [String] = []
    
    /// ...
}

Which, depending on your case, might make more sense. In this case, all access to ContentViewModel will need to happen on the @MainActor executor. In other words, access is isolated to the global @MainActor.

But you’re not limited to the main thread. You can also create custom global actors to group and isolate access to global or static state, making your app more robust in a concurrent world.

The Essential Swift Concurrency Course for a Seamless Swift 6 Migration.

Learn all about Swift Concurrency in my flagship course offering 13 modules, 78 lessons, videos, code examples, and an official certificate of completion.

Updated for Swift 6.2 and now available with a limited-time launch offer.

How to use a custom Global Actor?

We’ve seen examples of the standard @MainActor quite a few times now, but what if you want to define a custom global actor? 

Imagine having an app that does several things, including image processing. You don’t want multiple images to be processed at the same time. You also want anything that has to do with image processing be running synchronized. 

In this case, we can define a custom Global Actor named ImageProcessing:

@globalActor
actor ImageProcessing {
    static let shared = ImageProcessing()
}

The @globalActor attribute makes the ImageProcessing actor a globally accessible actor. It also requires the type to conform to the GlobalActor protocol, for which defining the static shared property will be enough.

Once defined, we can start using it just like we can with @MainActor. For example, we could use it on an image cache:

@ImageProcessing
final class ImageCache {
    
    var images: [URL: Data] = [:]
    
    /// Image cache logic...
}

Or maybe we have some kind of image filtering method:

@ImageProcessing
func applyFilter(_ inputImage: UIImage) -> UIImage {
    /// Apply filter to an image ...
}

Each of those will now execute on the ImageProcessing actor isolation domain, allowing you to centralize work related to image processing.

Preventing misuse of a Global Actor

The above example uses the @globalActor attribute directly attached to the actor instance itself. This has the downside that we’re not preventing anyone from using the ImageProcessing actor directly:

/// This is still possible:
ImageProcessing()

Doing so would create a new actor with a new executor underneath. This is not what we aimed to achieve—we want all image processing to be running on the same executor.

Therefore, I recommend making the initializer of your global actors private:

@globalActor actor ImageProcessing {
    public static let shared = ImageProcessing()
    
    private init() { }
}

The actor can’t be constructed directly anymore, preventing duplicate instances from being created.

Conclusion

Global actors are a powerful yet often underutilized feature of Swift Concurrency. While most developers are familiar with @MainActor, defining a custom global actors allows you to isolate access to specific domains of your application—such as image processing—ensuring thread-safe, predictable execution. Just remember to make the actor’s initializer private to prevent misuse.

This lesson partly represents one of the 78+ lessons of my Swift Concurrency course. If you’ve enjoyed it, I would love to invite you to the dedicated course itself: www.swiftconcurrencycourse.com

Thanks!

 
Antoine van der Lee

Written by

Antoine van der Lee

iOS Developer since 2010, former Staff iOS Engineer at WeTransfer and currently full-time Indie Developer & Founder at SwiftLee. Writing a new blog post every week related to Swift, iOS and Xcode. Regular speaker and workshop host.

Are you ready to

Turn your side projects into independence?

Learn my proven steps to transform your passion into profit.