Swift Utilities — Потокобезопасное свойство
За годы работы разработчиком iOS, я собрал множество инструментов и полезных штук, которые облегчают процесс разработки. В этой статье, я хочу поделиться одним из таких инструментов. Это будет не большая статья. Я покажу, как пользоваться этой утилитой, продемонстрирую её в действии. Надеюсь, что статья окажется полезной для вас.
При разработке очень важна безопасность данных при параллельном доступе к ним. В этой статье я покажу , как создать потокобезопасное свойство с использованием свойства-обёртки @SynchronizedLock
.
@SynchronizedLock
— это свойство-обёртка в Swift, обеспечивающая потокобезопасный доступ к переменной. Это означает, что при чтении и записи значения обёрнутой переменной, эти операции будут безопасно выполняться в разных потоках.
/// When using this property wrapper, you can ensure that reads and writes to the wrapped value are thread-safe.
/// Example:
///
///```swift
/// class SomeClass {
/// @SynchronizedLock var count: Int = 0
///
/// func main() {
/// DispatchQueue.global().async {
/// for _ in 1 ... 1000 {
/// count += 1
/// }
/// }
/// DispatchQueue.global().async {
/// for _ in 1 ... 1000 {
/// count += 1
/// }
/// }
/// }
///```
@propertyWrapper
public struct SynchronizedLock {
private var value: Value
private var lock = NSLock()
public var wrappedValue: Value {
get { lock.synchronized { value } }
set { lock.synchronized { value = newValue } }
}
public init(wrappedValue value: Value) {
self.value = value
}
}
private extension NSLock {
@discardableResult
func synchronized(_ block: () -> T) -> T {
lock()
defer { unlock() }
return block()
}
}
Как это работает?
Для обеспечения потокобезопасности, @SynchronizedLock
использует NSLock
. Это обеспечивает, что только один поток может доступаться к переменной в данный момент времени. Блокировка освобождается только после завершения операции, позволяя следующему потоку войти в критическую секцию.
Пример использования
Рассмотрим класс SomeClass
, где мы используем @SynchronizedLock
для потокобезопасного доступа к переменной count
.
class SomeClass {
@SynchronizedLock var count: Int = 0
func main() {
DispatchQueue.global().async {
for _ in 1 ... 1000 {
self.count += 1
}
}
DispatchQueue.global().async {
for _ in 1 ... 1000 {
self.count += 1
}
}
}
}
В этом примере, два разных потока пытаются изменить значение count
одновременно. Благодаря @SynchronizedLock
, операции инкремента выполняются без конфликтов и состояние гонки.
Преимущества и ограничения
Основное преимущество @SynchronizedLock
заключается в простоте использования и обеспечении безопасности данных в многопоточной среде. Однако использование блокировок может привести к снижению производительности, если они используются чрезмерно или в неоптимальном контексте.