Swift Utilities — Потокобезопасное свойство

63d9b5148e453addc63b8a760fa460b8.jpg

За годы работы разработчиком 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 заключается в простоте использования и обеспечении безопасности данных в многопоточной среде. Однако использование блокировок может привести к снижению производительности, если они используются чрезмерно или в неоптимальном контексте.

Еще статьи Swift Utilities:

© Habrahabr.ru