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.
Intrinsically, Arrays in Swift are not thread-safe. Arrays are 'value-typed' in the effect of assignment, initialization, and argument passing — creates an independent instance with its own unique copy of its data. Immutable arrays (declared using let) are thread-safe since it is read-only.
Unfortunately most of the Swift data types are not thread safe by default, so if you want to achieve thread-safety you usually had to work with serial queues or locks to guarantee the mutual exclusivity of a given variable.
Thread safe code can be safely called from multiple threads or concurrent tasks without causing any problems (data corruption, crashing, etc). Code that is not thread safe must only be run in one context at a time. An example of thread safe code is let a = ["thread-safe"].
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
I don't know why people take such complex approaches to such a simple thing
Don't abuse DispatchQueues
for locking. Using queue.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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With