Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: Shouldn't AnyCallable store(in:) be thread safe? #200

Closed
bofeizhu opened this issue Dec 13, 2020 · 3 comments
Closed

Question: Shouldn't AnyCallable store(in:) be thread safe? #200

bofeizhu opened this issue Dec 13, 2020 · 3 comments

Comments

@bofeizhu
Copy link

bofeizhu commented Dec 13, 2020

What if store(in:) were called from multiple threads? Wouldn't that break the current implementation?

@bofeizhu bofeizhu changed the title Question: Shouldn Question: Shouldn't AnyCallable store(in:) be thread safe? Dec 13, 2020
@maximkrouk
Copy link

I think it shouldn't, most of the time you don't need it so synchronization should be implemented only for specific cases as needed...

@broadwaylamb
Copy link
Member

Since the argument passed in this method is inout, the language prevents you from calling it simultaneously from multiple threads by crashing. See https://swift.org/blog/swift-5-exclusivity/.

@maximkrouk
Copy link

maximkrouk commented Jun 20, 2021

@bofeizhu you may use something like that

import Foundation

@propertyWrapper
public struct ThreadSafeWithLock<Value> {
  private var value: Value
  private let lock: NSLocking
  public var locksOnRead: Bool = false

  public init(
    wrappedValue: Value,
    _ lock: NSLocking = NSLock(),
    locksOnRead: Bool = false
  ) {
    self.value = wrappedValue
    self.lock = lock
    self.locksOnRead = locksOnRead
  }

  public var wrappedValue: Value {
    get {
      if locksOnRead {
        return lock.execute { value }
      } else {
        return value
      }
    }
    set { lock.store(newValue, in: &value) }
  }
}

@ThreadSafeWithLock
var subscriptions: Set<AnyCancellable> = []

publisher
  .sink { ... }
  .store(in &subscriptions)
import Foundation

extension NSLocking {
  /// Atomically stores new value in object
  @inlinable
  public func store<T>(_ value: T, in object: inout T) {
    mutate(&object, with: { $0 = value })
  }

  /// Atomically mutates object with closure
  @inlinable
  public func mutate<T: AnyObject>(_ object: T, with closure: (T) -> Void) {
    execute { closure(object) }
  }

  /// Atomically mutates object with closure
  @inlinable
  public func mutate<T>(_ object: inout T, with closure: (inout T) -> Void) {
    execute { closure(&object) }
  }

  /// Atomically mutates object with closure
  @inlinable
  public func set<T: AnyObject, Value>(
    _ object: T,
    _ keyPath: ReferenceWritableKeyPath<T, Value>,
    _ value: Value
  ) {
    execute { object[keyPath: keyPath] = value }
  }

  /// Atomically mutates object with closure
  @inlinable
  public func set<T, Value>(_ object: inout T, _ keyPath: WritableKeyPath<T, Value>, _ value: Value)
  {
    execute { object[keyPath: keyPath] = value }
  }

  /// Atomically executes a block of code
  @discardableResult
  @inlinable
  public func execute<T>(code closure: () -> T) -> T {
    lock()
    defer { unlock() }
    return closure()
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants