I've created this experiment. while ActorCounter is often more consistent than ClassCounter it is often not correct. Are static properties protected or not by actors?

import Foundation

let max = 1_000

actor ActorCounter {
    static var count: Int = 0
    static func increment() async {
        count += 1
    }
}

class ClassCounter {
    static var count: Int = 0
    static func increment() {
        count += 1
    }
}

// Increment concurrently using a Task Group
await withTaskGroup(of: Void.self, body: { group in
    for _ in 0..<max {
        group.addTask {
            await ActorCounter.increment()
        }
    }
})

// Increment concurrently using Dispatch
DispatchQueue.concurrentPerform(iterations: max) { _ in
    ClassCounter.increment()
}

print("Concurrent Perform is done")

var count = 0
for _ in 0..<max {
    count += 1
}

// make sure the work is really completed
let nanoseconds = UInt64(0.25 * Double(NSEC_PER_SEC))
try await Task.sleep(nanoseconds: nanoseconds)

print("Actor:", ActorCounter.count)
print("Class:", ClassCounter.count)
print("Non-Async:", count)

1 Like

Static methods are not part of any actor instance; they're global state just like static properties on other types. I wouldn't expect any reliable difference between the ActorCounter or ClassCounter here. If you want an actor guard that isn't tied to any particular instance, you can define a global actor, like MainActor, and decorate the methods you want to be guarded by it with the attribute for the global actor.

2 Likes

Thanks @Joe_Groff

Instead of implementing a Global Actor, which requires Xcode 13, I have made it into a singleton since we still need to allow Xcode 12.4 to be used.

Using @MainActor on that shared property should work, right?

@MainActor
    static let shared = MyType()

@MainActor is also Xcode 13+. If you need to support Xcode 12 / Swift <= 5.4, you have conditionalize all of your concurrency usage behind a check.

Edit: It may look like @MainActor is available but that's a compiler bug that's fixed on main but not yet in a release. Narrowly avoided that one myself.

2 Likes

What I've read here says custom global actors were possible with Xcode 13. I will have to try this out with Xcode 12.4, but I believe we have used @MainActor before. We have not created a custom global actor yet.

@MainActor is a global actor.

It is a special case which was supported earlier than the custom global actors, if what I am reading is accurate.

@MainActor was available slightly before the ability to define your own global actors, but both were generally part of Xcode 13 / Swift 5.5.

Wait, nevermind. All these versions are very confusing. I am using Xcode 14 and we support back to Xcode 13.4.