Pull to refresh

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

Level of difficultyEasy
Reading time2 min
Views1.2K

За годы работы разработчиком 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 number: Int = 0
///
///     func main() {
///         DispatchQueue.global().async { [weak self] in
///             for _ in 1 ... 1000 {
///                 self?.number = Int.random(in: 0 ..< 10)
///             }
///         }
///         DispatchQueue.global().async { [weak self] in
///             for _ in 1 ... 1000 {
///                 self?.number = Int.random(in: 0 ..< 10)
///             }
///         }
///     }
///```
@propertyWrapper
public struct SynchronizedLock<Value> {
    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<T>(_ block: () -> T) -> T {
        lock()
        defer { unlock() }
        return block()
    }
}

Как это работает?

Для обеспечения потокобезопасности, @SynchronizedLock использует NSLock. Это обеспечивает, что только один поток может доступаться к переменной в данный момент времени. Блокировка освобождается только после завершения операции, позволяя следующему потоку войти в критическую секцию.

Пример использования

Рассмотрим класс SomeClass, где мы используем @SynchronizedLock для потокобезопасного доступа к переменной count.

class SomeClass {
  
    @SynchronizedLock var number: Int = 0

    func main() {
        DispatchQueue.global().async { [weak self] in
            for _ in 1 ... 1000 {
                self?.number = Int.random(in: 0 ..< 10)
            }
        }
        DispatchQueue.global().async { [weak self] in
            for _ in 1 ... 1000 {
                self?.number = Int.random(in: 0 ..< 10)
            }
        }
    }
}

В этом примере, два разных потока пытаются изменить значение number одновременно. Благодаря @SynchronizedLock, операции инкремента выполняются без конфликтов и состояние гонки.

Преимущества и ограничения

Основное преимущество @SynchronizedLock заключается в простоте использования и обеспечении безопасности данных в многопоточной среде. Однако использование блокировок может привести к снижению производительности, если они используются чрезмерно или в неоптимальном контексте.

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

Tags:
Hubs:
Total votes 2: ↑1 and ↓1+2
Comments2

Articles