Reputation: 2810
I want to create a property wrapper
, that would store a callback block
, and execute it every time, when it's value changes. Something like simple KVO
. It works fine, but there is one problem with it. If I use the property itself in this callback block, then I get an error:
Simultaneous accesses to 0x6000007fc3d0, but modification requires exclusive access
From what I understand this is because the property itself is still being written to, while this block is executing, and this is why it can't be read.
Lets add some code, to show what I mean:
@propertyWrapper
struct ReactingProperty<T> {
init(wrappedValue: T) {
self.wrappedValue = wrappedValue
}
public var wrappedValue: T {
didSet {
reaction?(wrappedValue)
}
}
public var projectedValue: Self {
get { self }
set { self = newValue }
}
private var reaction: ((_ value: T) -> Void)?
public mutating func setupReaction(_ reaction: @escaping (_ value: T) -> Void) {
self.reaction = reaction
}
}
And AppDelegate
:
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
@ReactingProperty
var testInt = 0
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// 1. This will pass correctly.
$testInt.setupReaction { (value) in
print(value)
}
testInt = 1
// 2. This will cause crash, because I access testInt in this block, that is executed when value changes.
$testInt.setupReaction { [unowned self] (value) in
print(self.testInt)
}
testInt = 2
return true
}
}
I have a few workarounds for this, but none of theme is really what I need for various reasons.
If I access the value in the block
from this block argument
instead, and pass this argument in value didSet
, then it works fine. But this forces me to always use it this way, and I would like to use this with the code, that contains various other callbacks, and sometimes it would be more convenient for me to be able to access this value directly as well.
I can execute the callback block
asynchronously (DispachQueue.main.async { self.block?(value) })
. But this also is not the best for my case.
Use combine
instead. I will probably, but I would also like to remain this functionality for now. Also Im just curious on this issue.
Can this somehow be overcome? What variable is actually causing this read-write
access error? Is this the value inside propertyWrapper
or struct with propertyWrapper
itself?
I think it's propertyWrapper struct
accessing that causes this, and not its internal value, but Im not sure.
Upvotes: 2
Views: 396
Reputation: 2810
I think I have found the right solution. Just change from struct to class. Then read/write access is no issue.
@propertyWrapper
class ReactingProperty<T> {
init(wrappedValue: T) {
self.wrappedValue = wrappedValue
}
public var wrappedValue: T {
didSet {
reaction?(wrappedValue)
}
}
public lazy var projectedValue = self
private var reaction: ((_ value: T) -> Void)?
public mutating func setupReaction(_ reaction: @escaping (_ value: T) -> Void) {
self.reaction = reaction
}
}
Upvotes: 3