Create thread safe array in Swift
IosArraysSwiftMultithreadingRead WriteIos Problem Overview
I have a threading problem in Swift. I have an array with some objects in it. Over a delegate the class gets new objects about every second. After that I have to check if the objects are already in the array, so I have to update the object, otherwise I have to delete / add the new object.
If I add a new object I have to fetch some data over the network first. This is handelt via a block.
Now my problem is, how to I synchronic this tasks?
I have tried a dispatch_semaphore, but this one blocks the UI, until the block is finished.
I have also tried a simple bool variable, which checks if the block is currently executed and skips the compare method meanwhile.
But both methods are not ideal.
What's the best way to manage the array, I don't wanna have duplicate data in the array.
Ios Solutions
Solution 1 - Ios
Update for Swift
The recommended pattern for thread-safe access is using dispatch barrier
:
let queue = DispatchQueue(label: "thread-safe-obj", attributes: .concurrent)
// write
queue.async(flags: .barrier) {
// perform writes on data
}
// read
var value: ValueType!
queue.sync {
// perform read and assign value
}
return value
Solution 2 - Ios
I don't know why people take such complex approaches to such a simple thing
-
Don't abuse
DispatchQueues
for locking. Usingqueue.sync
is nothing more than acquiring a lock and dispatching work to another thread while the lock (DispatchGroup
) waits. It is not just unnecessary, but also can have side effects depending on what you are locking. You can look it up yourself in the GCD Source.Also GCD does not mix well with the new structured concurrency APIs!
-
Don't use
objc_sync_enter/exit
, those are used by ObjCs@synchronized
which will implicitly bridge Swift collections to a ObjC counterpart, which is also unnecessary. And it is a legacy API.
Just define a lock, and guard your collection access.
var lock = NSLock()
var a = [1, 2, 3]
lock.lock()
a.append(4)
lock.unlock()
If you want to make your life a bit easier, define a small extension.
extension NSLock {
@discardableResult
func with<T>(_ block: () throws -> T) rethrows -> T {
lock()
defer { unlock() }
return try block()
}
}
let lock = NSLock()
var a = [1, 2, 3]
lock.with { a.append(4) }
You can also define a @propertyWrapper
to make your member var
s atomic.
@propertyWrapper
struct Atomic<Value> {
private let lock = NSLock()
private var value: Value
init(default: Value) {
self.value = `default`
}
var wrappedValue: Value {
get {
lock.lock()
defer { lock.unlock() }
return value
}
set {
lock.lock()
value = newValue
lock.unlock()
}
}
}
Last but not least for primitive atomic types there is also Swift Atomics
Solution 3 - Ios
Kirsteins's answer is correct, but for convenience, I've updated that answer with Amol Chaudhari and Rob's suggestions for using a concurrent queue with async barrier to allow concurrent reads but block on writes.
I've also wrapped some other array functions that were useful to me.
public class SynchronizedArray<T> {
private var array: [T] = []
private let accessQueue = dispatch_queue_create("SynchronizedArrayAccess", DISPATCH_QUEUE_CONCURRENT)
public func append(newElement: T) {
dispatch_barrier_async(self.accessQueue) {
self.array.append(newElement)
}
}
public func removeAtIndex(index: Int) {
dispatch_barrier_async(self.accessQueue) {
self.array.removeAtIndex(index)
}
}
public var count: Int {
var count = 0
dispatch_sync(self.accessQueue) {
count = self.array.count
}
return count
}
public func first() -> T? {
var element: T?
dispatch_sync(self.accessQueue) {
if !self.array.isEmpty {
element = self.array[0]
}
}
return element
}
public subscript(index: Int) -> T {
set {
dispatch_barrier_async(self.accessQueue) {
self.array[index] = newValue
}
}
get {
var element: T!
dispatch_sync(self.accessQueue) {
element = self.array[index]
}
return element
}
}
}
UPDATE This is the same code, updated for Swift3.
public class SynchronizedArray<T> {
private var array: [T] = []
private let accessQueue = DispatchQueue(label: "SynchronizedArrayAccess", attributes: .concurrent)
public func append(newElement: T) {
self.accessQueue.async(flags:.barrier) {
self.array.append(newElement)
}
}
public func removeAtIndex(index: Int) {
self.accessQueue.async(flags:.barrier) {
self.array.remove(at: index)
}
}
public var count: Int {
var count = 0
self.accessQueue.sync {
count = self.array.count
}
return count
}
public func first() -> T? {
var element: T?
self.accessQueue.sync {
if !self.array.isEmpty {
element = self.array[0]
}
}
return element
}
public subscript(index: Int) -> T {
set {
self.accessQueue.async(flags:.barrier) {
self.array[index] = newValue
}
}
get {
var element: T!
self.accessQueue.sync {
element = self.array[index]
}
return element
}
}
}
Solution 4 - Ios
My approach to this problem was using serial dispatch queue, to synchronise access to boxed array. It will block the thread when you try to get the value at index and queue is really busy, but that's the problem with locks as well.
public class SynchronizedArray<T> {
private var array: [T] = []
private let accessQueue = dispatch_queue_create("SynchronizedArrayAccess", DISPATCH_QUEUE_SERIAL)
public func append(newElement: T) {
dispatch_async(self.accessQueue) {
self.array.append(newElement)
}
}
public subscript(index: Int) -> T {
set {
dispatch_async(self.accessQueue) {
self.array[index] = newValue
}
}
get {
var element: T!
dispatch_sync(self.accessQueue) {
element = self.array[index]
}
return element
}
}
}
var a = SynchronizedArray<Int>()
a.append(1)
a.append(2)
a.append(3)
// can be empty as this is non-thread safe access
println(a.array)
// thread-safe synchonized access
println(a[0])
println(a[1])
println(a[2])
Solution 5 - Ios
Details
- Xcode 10.1 (10B61), Swift 4.2
- Xcode 10.2.1 (10E1001), Swift 5
Solution
import Foundation
// https://developer.apple.com/documentation/swift/rangereplaceablecollection
struct AtomicArray<T>: RangeReplaceableCollection {
typealias Element = T
typealias Index = Int
typealias SubSequence = AtomicArray<T>
typealias Indices = Range<Int>
fileprivate var array: Array<T>
var startIndex: Int { return array.startIndex }
var endIndex: Int { return array.endIndex }
var indices: Range<Int> { return array.indices }
func index(after i: Int) -> Int { return array.index(after: i) }
private var semaphore = DispatchSemaphore(value: 1)
fileprivate func _wait() { semaphore.wait() }
fileprivate func _signal() { semaphore.signal() }
}
// MARK: - Instance Methods
extension AtomicArray {
init<S>(_ elements: S) where S : Sequence, AtomicArray.Element == S.Element {
array = Array<S.Element>(elements)
}
init() { self.init([]) }
init(repeating repeatedValue: AtomicArray.Element, count: Int) {
let array = Array(repeating: repeatedValue, count: count)
self.init(array)
}
}
// MARK: - Instance Methods
extension AtomicArray {
public mutating func append(_ newElement: AtomicArray.Element) {
_wait(); defer { _signal() }
array.append(newElement)
}
public mutating func append<S>(contentsOf newElements: S) where S : Sequence, AtomicArray.Element == S.Element {
_wait(); defer { _signal() }
array.append(contentsOf: newElements)
}
func filter(_ isIncluded: (AtomicArray.Element) throws -> Bool) rethrows -> AtomicArray {
_wait(); defer { _signal() }
let subArray = try array.filter(isIncluded)
return AtomicArray(subArray)
}
public mutating func insert(_ newElement: AtomicArray.Element, at i: AtomicArray.Index) {
_wait(); defer { _signal() }
array.insert(newElement, at: i)
}
mutating func insert<S>(contentsOf newElements: S, at i: AtomicArray.Index) where S : Collection, AtomicArray.Element == S.Element {
_wait(); defer { _signal() }
array.insert(contentsOf: newElements, at: i)
}
mutating func popLast() -> AtomicArray.Element? {
_wait(); defer { _signal() }
return array.popLast()
}
@discardableResult mutating func remove(at i: AtomicArray.Index) -> AtomicArray.Element {
_wait(); defer { _signal() }
return array.remove(at: i)
}
mutating func removeAll() {
_wait(); defer { _signal() }
array.removeAll()
}
mutating func removeAll(keepingCapacity keepCapacity: Bool) {
_wait(); defer { _signal() }
array.removeAll()
}
mutating func removeAll(where shouldBeRemoved: (AtomicArray.Element) throws -> Bool) rethrows {
_wait(); defer { _signal() }
try array.removeAll(where: shouldBeRemoved)
}
@discardableResult mutating func removeFirst() -> AtomicArray.Element {
_wait(); defer { _signal() }
return array.removeFirst()
}
mutating func removeFirst(_ k: Int) {
_wait(); defer { _signal() }
array.removeFirst(k)
}
@discardableResult mutating func removeLast() -> AtomicArray.Element {
_wait(); defer { _signal() }
return array.removeLast()
}
mutating func removeLast(_ k: Int) {
_wait(); defer { _signal() }
array.removeLast(k)
}
@inlinable public func forEach(_ body: (Element) throws -> Void) rethrows {
_wait(); defer { _signal() }
try array.forEach(body)
}
mutating func removeFirstIfExist(where shouldBeRemoved: (AtomicArray.Element) throws -> Bool) {
_wait(); defer { _signal() }
guard let index = try? array.firstIndex(where: shouldBeRemoved) else { return }
array.remove(at: index)
}
mutating func removeSubrange(_ bounds: Range<Int>) {
_wait(); defer { _signal() }
array.removeSubrange(bounds)
}
mutating func replaceSubrange<C, R>(_ subrange: R, with newElements: C) where C : Collection, R : RangeExpression, T == C.Element, AtomicArray<Element>.Index == R.Bound {
_wait(); defer { _signal() }
array.replaceSubrange(subrange, with: newElements)
}
mutating func reserveCapacity(_ n: Int) {
_wait(); defer { _signal() }
array.reserveCapacity(n)
}
public var count: Int {
_wait(); defer { _signal() }
return array.count
}
public var isEmpty: Bool {
_wait(); defer { _signal() }
return array.isEmpty
}
}
// MARK: - Get/Set
extension AtomicArray {
// Single action
func get() -> [T] {
_wait(); defer { _signal() }
return array
}
mutating func set(array: [T]) {
_wait(); defer { _signal() }
self.array = array
}
// Multy actions
mutating func get(closure: ([T])->()) {
_wait(); defer { _signal() }
closure(array)
}
mutating func set(closure: ([T]) -> ([T])) {
_wait(); defer { _signal() }
array = closure(array)
}
}
// MARK: - Subscripts
extension AtomicArray {
subscript(bounds: Range<AtomicArray.Index>) -> AtomicArray.SubSequence {
get {
_wait(); defer { _signal() }
return AtomicArray(array[bounds])
}
}
subscript(bounds: AtomicArray.Index) -> AtomicArray.Element {
get {
_wait(); defer { _signal() }
return array[bounds]
}
set(value) {
_wait(); defer { _signal() }
array[bounds] = value
}
}
}
// MARK: - Operator Functions
extension AtomicArray {
static func + <Other>(lhs: Other, rhs: AtomicArray) -> AtomicArray where Other : Sequence, AtomicArray.Element == Other.Element {
return AtomicArray(lhs + rhs.get())
}
static func + <Other>(lhs: AtomicArray, rhs: Other) -> AtomicArray where Other : Sequence, AtomicArray.Element == Other.Element {
return AtomicArray(lhs.get() + rhs)
}
static func + <Other>(lhs: AtomicArray, rhs: Other) -> AtomicArray where Other : RangeReplaceableCollection, AtomicArray.Element == Other.Element {
return AtomicArray(lhs.get() + rhs)
}
static func + (lhs: AtomicArray<Element>, rhs: AtomicArray<Element>) -> AtomicArray {
return AtomicArray(lhs.get() + rhs.get())
}
static func += <Other>(lhs: inout AtomicArray, rhs: Other) where Other : Sequence, AtomicArray.Element == Other.Element {
lhs._wait(); defer { lhs._signal() }
lhs.array += rhs
}
}
// MARK: - CustomStringConvertible
extension AtomicArray: CustomStringConvertible {
var description: String {
_wait(); defer { _signal() }
return "\(array)"
}
}
// MARK: - Equatable
extension AtomicArray where Element : Equatable {
func split(separator: Element, maxSplits: Int, omittingEmptySubsequences: Bool) -> [ArraySlice<Element>] {
_wait(); defer { _signal() }
return array.split(separator: separator, maxSplits: maxSplits, omittingEmptySubsequences: omittingEmptySubsequences)
}
func firstIndex(of element: Element) -> Int? {
_wait(); defer { _signal() }
return array.firstIndex(of: element)
}
func lastIndex(of element: Element) -> Int? {
_wait(); defer { _signal() }
return array.lastIndex(of: element)
}
func starts<PossiblePrefix>(with possiblePrefix: PossiblePrefix) -> Bool where PossiblePrefix : Sequence, Element == PossiblePrefix.Element {
_wait(); defer { _signal() }
return array.starts(with: possiblePrefix)
}
func elementsEqual<OtherSequence>(_ other: OtherSequence) -> Bool where OtherSequence : Sequence, Element == OtherSequence.Element {
_wait(); defer { _signal() }
return array.elementsEqual(other)
}
func contains(_ element: Element) -> Bool {
_wait(); defer { _signal() }
return array.contains(element)
}
static func != (lhs: AtomicArray<Element>, rhs: AtomicArray<Element>) -> Bool {
lhs._wait(); defer { lhs._signal() }
rhs._wait(); defer { rhs._signal() }
return lhs.array != rhs.array
}
static func == (lhs: AtomicArray<Element>, rhs: AtomicArray<Element>) -> Bool {
lhs._wait(); defer { lhs._signal() }
rhs._wait(); defer { rhs._signal() }
return lhs.array == rhs.array
}
}
Usage sample 1
import Foundation
// init
var array = AtomicArray<Int>()
print(array)
array = AtomicArray(repeating: 0, count: 5)
print(array)
array = AtomicArray([1,2,3,4,5,6,7,8,9])
print(array)
// add
array.append(0)
print(array)
array.append(contentsOf: [5,5,5])
print(array)
// filter
array = array.filter { $0 < 7 }
print(array)
// map
let strings = array.map { "\($0)" }
print(strings)
// insert
array.insert(99, at: 5)
print(array)
array.insert(contentsOf: [2, 2, 2], at: 0)
print(array)
// pop
_ = array.popLast()
print(array)
_ = array.popFirst()
print(array)
// remove
array.removeFirst()
print(array)
array.removeFirst(3)
print(array)
array.remove(at: 2)
print(array)
array.removeLast()
print(array)
array.removeLast(5)
print(array)
array.removeAll { $0%2 == 0 }
print(array)
array = AtomicArray([1,2,3,4,5,6,7,8,9,0])
array.removeSubrange(0...2)
print(array)
array.replaceSubrange(0...2, with: [0,0,0])
print(array)
array.removeAll()
print(array)
array.set(array: [1,2,3,4,5,6,7,8,9,0])
print(array)
// subscript
print(array[0])
array[0] = 100
print(array)
print(array[1...4])
// operator functions
array = [1,2,3] + AtomicArray([4,5,6])
print(array)
array = AtomicArray([4,5,6]) + [1,2,3]
print(array)
array = AtomicArray([1,2,3]) + AtomicArray([4,5,6])
print(array)
Usage sample 2
import Foundation
var arr = AtomicArray([0,1,2,3,4,5])
for i in 0...1000 {
// Single actions
DispatchQueue.global(qos: .background).async {
usleep(useconds_t(Int.random(in: 100...10000)))
let num = i*i
arr.append(num)
print("arr.append(\(num)), background queue")
}
DispatchQueue.global(qos: .default).async {
usleep(useconds_t(Int.random(in: 100...10000)))
arr.append(arr.count)
print("arr.append(\(arr.count)), default queue")
}
// multy actions
DispatchQueue.global(qos: .utility).async {
arr.set { array -> [Int] in
var newArray = array
newArray.sort()
print("sort(), .utility queue")
return newArray
}
}
}
Solution 6 - Ios
A minor detail: In Swift 3 (at least in Xcode 8 Beta 6), the syntax for queues changed significantly. The important changes to @Kirsteins' answer will be:
private let accessQueue = DispatchQueue(label: "SynchronizedArrayAccess")
txAccessQueue.async() {
// Your async code goes here...
}
txAccessQueue.sync() {
// Your sync code goes here...
}
Solution 7 - Ios
Swift-Nio & Vapor Swift
For those of you using Swift-Nio (or Vapor Swift which is based on Swift-Nio), there's a built in solution for this problem:
class MyClass {
let lock = Lock()
var myArray: Array<Int> = []
func networkRequestWhatEver() {
lock.withLock {
array.append(someValue)
}
}
}
Note that you should use the same Lock
object when modifing the same Array
object (or Dictionary
, etc.).
Solution 8 - Ios
Thread-safe Data Structures with Actors
As of Swift 5.5 you can express this with an actor:
actor SyncArray<T> {
private var buffer: [T]
init<S: Sequence>(_ elements: S) where S.Element == T {
buffer = Array(elements)
}
var count: Int {
buffer.count
}
func append(_ element: T) {
buffer.append(element)
}
@discardableResult
func remove(at index: Int) -> T {
buffer.remove(at: index)
}
}
Not only it makes the code simpler and less error prone, but it makes more explicit the potential race condition pointed out in an other answer:
Task {
let array = SyncArray([1])
if await array.count == 1 {
await array.remove(at: 0)
}
}
There are two suspension points here, meaning that by the time .remove(at:)
is called, the array count
could have changed.
Such read-then-write operation must be atomic to be consistent, thus it should be defined as a method on the actor instead:
extension SyncArray {
func removeLastIfSizeOfOne() {
if buffer.count == 1 {
buffer.remove(at: 0)
}
}
}
Above, the absence of suspension points indicates that the operation is performed atomically. Another solution that works without writing an extension is to use the isolated
keyword like this:
func removeLastIfSizeOfOne<T>(_ array: isolated SyncArray<T>) {
if array == 1 {
array(at: 0)
}
}
This will isolate the passed actor for the duration of the whole call instead of at each of its suspension points. Calling this function requires only one suspension point.
Solution 9 - Ios
Here is the answer for Swift 4,
let queue = DispatchQueue(label: "com.readerWriter", qos: .background, attributes: .concurrent)
var safeArray: [String] = []
subscript(index: Int) -> String {
get {
queue.sync {
return safeArray[index]
}
}
set(newValue) {
queue.async(flags: .barrier) { [weak self] in
self?.safeArray[index] = newValue
}
}
}
Solution 10 - Ios
I think dispatch_barriers are worth looking into. Using gcd for synchronicity is more intuitive to me than using synchronize keyword to avoid state mutation from multiple threads.
https://mikeash.com/pyblog/friday-qa-2011-10-14-whats-new-in-gcd.html
Solution 11 - Ios
Approach:
Use DispatchQueue
to synchronise
Refer:
http://basememara.com/creating-thread-safe-arrays-in-swift/
Code:
Below is a crude implementation of a thread safe array, you can fine tune it.
public class ThreadSafeArray<Element> {
private var elements : [Element]
private let syncQueue = DispatchQueue(label: "Sync Queue",
qos: .default,
attributes: .concurrent,
autoreleaseFrequency: .inherit,
target: nil)
public init() {
elements = []
}
public init(_ newElements: [Element]) {
elements = newElements
}
//MARK: Non-mutating
public var first : Element? {
return syncQueue.sync {
elements.first
}
}
public var last : Element? {
return syncQueue.sync {
elements.last
}
}
public var count : Int {
return syncQueue.sync {
elements.count
}
}
public subscript(index: Int) -> Element {
get {
return syncQueue.sync {
elements[index]
}
}
set {
syncQueue.sync(flags: .barrier) {
elements[index] = newValue
}
}
}
public func reversed() -> [Element] {
return syncQueue.sync {
elements.reversed()
}
}
public func flatMap<T>(_ transform: (Element) throws -> T?) rethrows -> [T] {
return try syncQueue.sync {
try elements.flatMap(transform)
}
}
public func filter(_ isIncluded: (Element) -> Bool) -> [Element] {
return syncQueue.sync {
elements.filter(isIncluded)
}
}
//MARK: Mutating
public func append(_ element: Element) {
syncQueue.sync(flags: .barrier) {
elements.append(element)
}
}
public func append<S>(contentsOf newElements: S) where Element == S.Element, S : Sequence {
syncQueue.sync(flags: .barrier) {
elements.append(contentsOf: newElements)
}
}
public func remove(at index: Int) -> Element? {
var element : Element?
syncQueue.sync(flags: .barrier) {
if elements.startIndex ..< elements.endIndex ~= index {
element = elements.remove(at: index)
}
else {
element = nil
}
}
return element
}
}
extension ThreadSafeArray where Element : Equatable {
public func index(of element: Element) -> Int? {
return syncQueue.sync {
elements.index(of: element)
}
}
}
Solution 12 - Ios
firstly, objc_sync_enter not works
objc_sync_enter(array)
defer {
objc_sync_exit(array)
}
objc_sync_enter is an extremely low-level primitive, and isn't intended to be used directly. It's an implementation detail of the old @synchronized system in ObjC.
for swift, should use like this, just as @Kirsteins said, and I suggest sync instead of async:
private let syncQueue = DispatchQueue(label:"com.test.LockQueue")
func test(){
self.syncQueue.sync{
// thread safe code here
}
}
Solution 13 - Ios
To improve the accepted answer I would suggest using defer:
objc_sync_enter(array)
defer {
objc_sync_exit(array)
}
// manipulate the array
and the second one
func sync(lock: NSObject, closure: () -> Void) {
objc_sync_enter(lock)
defer {
objc_sync_exit(lock)
}
closure()
}
Solution 14 - Ios
Swift Thread safe collection
The main and most common idea of making something(e.g. Collection) thread safe in Swift is:
- Custom(local) concurrent queue
- Synchronous reading. Reading a critical section(shared resource) via
sync
- Asynchronous writing with barrier
Solution 15 - Ios
If you want thread-safe interaction with your array, you must synchronize your access. There are many alternatives suggested (and a few that have been omitted), so let us survey the various synchronization alternatives:
-
Serial dispatch queue: This is a straightforward and intuitive GCD pattern.
-
Reader-writer pattern with concurrent queue: This is an elegant refinement of the serial dispatch queue pattern, using concurrent queue with asynchronous “writes” (so the caller does not wait for the write to finish) with a barrier (to prevent any interaction concurrent with a “write”), but it offers concurrent “reads” (allowing greater concurrency during “reads”). This is a sophisticated and appealing pattern, but in practice, it is only useful if the benefits of concurrent “reads” and asynchronous “writes” outweigh the GCD overhead.
-
Locks:
-
NSLock
is a fast and simple locking mechanism that is more performant than any of the GCD alternatives for most scenarios:extension NSLocking { func synchronized<T>(_ block: () throws -> T) rethrows -> T { lock() defer { unlock() } return try block() } }
-
os_unfair_lock
is another locking mechanism, which is even faster thanNSLock
, but is a little more complicated to use from Swift. See https://stackoverflow.com/a/66525671/1271826. But in those rare cases where performance is of paramount concern, unfair locks are a compelling solution.
-
-
The Objective-C
objc_sync_enter
andobjc_sync_exit
API: This is not of practical interest in the Swift world. -
Semaphores: It is conceptually similar to lock-based approaches, but is generally slower than any of the lock-based approaches and can be disregarded in this conversation.
-
Actors: A synchronization mechanism provided by the Swift 5.5 concurrency system. See Protect mutable state with Swift actors.
In short, if using async
-await
, actors are the logical alternative. If not yet adopting the Swift concurrency system, I would gravitate to a lock-based approach (which is simple and fast) or, in rare cases, the GCD reader-writer approach.
In practice, the choice of synchronization mechanism is not relevant in most use cases. (And if you are doing so many synchronizations that the performance difference becomes material, you might want to consider how to reduce the number of synchronization points before dwelling on the particular mechanism.) That having been said, the older synchronization mechanisms (semaphores, objc_sync_enter
, etc.) simply would not be contemplated anymore.
Having outlined the possible synchronization mechanisms, the next question is at what level one performs the synchronization. Specifically, more than once, property wrappers for the entire array have been proposed. This is, invariably, the wrong place to synchronize. The property wrapper approach provides atomic access to the array (which is not quite the same thing as thread-safety), but you generally need a higher level of abstraction. E.g. if one thread is adding elements and while another is reading or removing, you often want each of these high-level tasks to be synchronized, not just the individual accesses of the array.
Solution 16 - Ios
There's a great answer here which is threadsafe and doesn't block concurrent reads: https://stackoverflow.com/a/15936959/2050665
It's written in Objective C, but porting to Swift is trivial.
@property (nonatomic, readwrite, strong) dispatch_queue_t thingQueue;
@property (nonatomic, strong) NSObject *thing;
- (id)init {
...
_thingQueue = dispatch_queue_create("...", DISPATCH_QUEUE_CONCURRENT);
...
}
- (NSObject *)thing {
__block NSObject *thing;
dispatch_sync(self.thingQueue, ^{
thing = _thing;
});
return thing;
}
- (void)setThing:(NSObject *)thing {
dispatch_barrier_async(self.thingQueue, ^{
_thing = thing;
});
}