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