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)
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.
@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.
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.