EDIT: I leave this first answer in to keep the history, but a hint of OOPer has lead to a total different view (see next answer).
First of all: I'm quite impressed how fast and well educated the answers flow in (we are on a weekend!)
So the suggestion of Itai Ferber was very good one, and as he asked, I did some performance tests, just to give him something in return ;-)
I run the test with the attached code in a playground. And as you see this is by far not a well designed performance test, it is just a simple test to get a gist of the performance impact. I did several iterations (see table below).
Again: I did it in a Playground, so the absolute times will be much better in a "real" app, but the differences between the tests will be very similar.
Key findings:
interactions shows linear behavior (as expected)
"My" solution (test1) is about 15 times slower than an "un-queued" global variable (test0)
I did a test were I used an additional global variable as the helper variable (test2), this is slightly faster, but not a real break through
The suggested solution from Itai Ferber (test3) is about 6 to 7 times slower that the pure global variable (test0)... so it is twice as fast as "my" solution
So alternative 3 does not only look better, as it doesn't need the overhead for the helper variable it is also faster.

// the queue to synchronze data access, it's a concurrent one
fileprivate let globalDataQueue = DispatchQueue(
label: "com.ACME.globalDataQueue",
attributes: .concurrent)
// ------------------------------------------------------------------------------------------------
// Base Version: Just a global variable
// this is the global "variable" we worked with
var globalVariable : Int = 0
// ------------------------------------------------------------------------------------------------
// Alternative 1: with concurrent queue, helper variable insider getter
// As I used a calculated variable, to overcome the compiler errors, we need a helper variable
// to store the actual value.
var globalVariable1_Value : Int = 0
// this is the global "variable" we worked with
var globalVariable1 : Int {
set (newValue) {
globalDataQueue.async(flags: .barrier) {
globalVariable1_Value = newValue
}
}
get {
// we need a helper variable to store the result.
// inside a void closure you are not allow to "return"
var globalVariable1_Helper : Int = 0
globalDataQueue.sync{
globalVariable1_Helper = globalVariable1_Value
}
return globalVariable1_Helper
}
}
// ------------------------------------------------------------------------------------------------
// Alternative 2: with concurrent queue, helper variable as additional global variable
// As I used a calculated variable, to overcome the compiler errors, we need a helper variable
// to store the actual value.
var globalVariable2_Value : Int = 0
var globalVariable2_Helper : Int = 0
// this is the global "variable" we worked with
var globalVariable2 : Int {
// the setter
set (newValue) {
globalDataQueue.async(flags: .barrier) {
globalVariable2_Value = newValue
}
}
// the getter
get {
globalDataQueue.sync{
globalVariable2_Helper = globalVariable2_Value
}
return globalVariable2_Helper
}
}
// ------------------------------------------------------------------------------------------------
// Alternative 3: with concurrent queue, no helper variable as Itai Ferber suggested
// "compact" design
var globalVariable3_Value : Int = 0
var globalVariable3 : Int {
set (newValue) {
globalDataQueue.async(flags: .barrier) { globalVariable3_Value = newValue }
}
get {
return globalDataQueue.sync { globalVariable3_Value }
}
}
// ------------------------------------------------------------------------------------------------
// -- Testing
// variable for read test
var testVar = 0
let numberOfInterations = 2
// Test 0
print ("\nStart test0: simple global variable, not thread safe")
let startTime = CFAbsoluteTimeGetCurrent()
for _ in 0 ..< numberOfInterations {
testVar = globalVariable
globalVariable += 1
}
let endTime = CFAbsoluteTimeGetCurrent()
let timeDiff = endTime - startTime
print("globalVariable == \(globalVariable), test0 time needed \(timeDiff) seconds")
// Test 1
testVar = 0
print ("\nStart test1: concurrent queue, helper variable inside getter")
let startTime1 = CFAbsoluteTimeGetCurrent()
for _ in 0 ..< numberOfInterations {
testVar = globalVariable1
globalVariable1 += 1
}
let endTime1 = CFAbsoluteTimeGetCurrent()
let timeDiff1 = endTime1 - startTime1
print("globalVariable == \(globalVariable1), test1 time needed \(timeDiff1) seconds")
// Test 2
testVar = 0
print ("\nStart test2: with concurrent queue, helper variable as an additional global variable")
let startTime2 = CFAbsoluteTimeGetCurrent()
for _ in 0 ..< numberOfInterations {
testVar = globalVariable2
globalVariable2 += 1
}
let endTime2 = CFAbsoluteTimeGetCurrent()
let timeDiff2 = endTime2 - startTime2
print("globalVariable == \(globalVariable2), test2 time needed \(timeDiff2) seconds")
// Test 3
testVar = 0
print ("\nStart test3: with concurrent queue, no helper variable as Itai Ferber suggested")
let startTime3 = CFAbsoluteTimeGetCurrent()
for _ in 0 ..< numberOfInterations {
testVar = globalVariable3
globalVariable3 += 1
}
let endTime3 = CFAbsoluteTimeGetCurrent()
let timeDiff3 = endTime3 - startTime3
print("globalVariable == \(globalVariable3), test3 time needed \(timeDiff3) seconds")