I would like to create a set of objects that exhibit the following behavior:
- Each has a BOOL property -- call it
dataLocked
-- that is initially false. - Each has a set of stored properties whose values may be set, but not read, whenever
dataLocked == false
. - Those same stored properties may be read, but not set, whenever
dataLocked == true
dataLocked
can be set only once.
Below is a sample implementation. Is there any Swifty way to achieve this without having to reproduce all those get and set conditions for every property of every object?
The neatest solution I believe would be to create a Property Wrapper, but I haven't found any way to make the wrapper change its behaviors based on the value of the `locked` property in the enclosing object.
class ImmutableObjectBase {
var dataLocked: Bool = false {
didSet { dataLocked = true }
}
private var _someIntValue: Int = 42
var someIntValue: Int {
get {
precondition(dataLocked, "Cannot access object properties until object is locked")
return _someIntValue
}
set {
precondition(!dataLocked, "Cannot modify object properties after object is locked")
_someIntValue = newValue
}
}
}
let i = ImmutableObjectBase()
i.someIntValue = 100
i.dataLocked = true // or false, it doesn't matter!
print (i.someIntValue) // 100
print (i.dataLocked) // true
i.someIntValue = 200 // aborts
CodePudding user response:
I found a solution for your problem, but it is probably not the cleanest way. You might find a better way if you check this or this.
I managed to lock and unlock the properties by using this tutorial:
import Foundation
import Combine
public class Locker: ObservableObject {
@Published public var isLocked = false
public static let shared = Locker()
private init() {}
}
@propertyWrapper
struct Lockable<Value> {
private var _wrappedValue: Value
init(wrappedValue: Value) {
self._wrappedValue = wrappedValue
}
var wrappedValue: Value {
get {
precondition(Locker.shared.isLocked, "Cannot access object properties until object is locked")
return _wrappedValue
}
set {
precondition(!Locker.shared.isLocked, "Cannot modify object properties after object is locked")
_wrappedValue = newValue
}
}
}
class ImmutableObjectBase {
var isLocked: Bool = false {
didSet {
Locker.shared.isLocked = self.isLocked
}
}
@Lockable var someString: String = "initial"
@Lockable var someInt: Int = 1
}
var i = ImmutableObjectBase()
i.isLocked = true
print(i.someInt, i.someString) // 1, initial
i.isLocked = false
i.someInt = 2
i.someString = "new value"
i.isLocked = true
print(i.someInt, i.someString) // 2, new value
EDIT:
I just found a similar question with an accepted answer. Check here
CodePudding user response:
import Foundation
protocol PropertyWrapperWithLockableObject {
var enclosingObject: LockableObjectBase! {get set}
}
@propertyWrapper
class Lockable<Value>: PropertyWrapperWithLockableObject {
private var _wrappedValue: Value
var enclosingObject: LockableObjectBase!
init (wrappedValue: Value) { self._wrappedValue = wrappedValue }
var wrappedValue: Value {
get {
precondition(enclosingObject.isLocked, "Cannot access object properties until object is locked")
return _wrappedValue
}
set {
precondition(!enclosingObject.isLocked, "Cannot modify object properties after object is locked")
_wrappedValue = newValue
}
}
}
class LockableObjectBase {
internal var isLocked: Bool = false {
didSet { isLocked = true }
}
init () {
let mirror = Mirror(reflecting: self)
for child in mirror.children {
if var child = child.value as? PropertyWrapperWithLockableObject {
child.enclosingObject = self
}
}
}
}
Usage:
class DataObject: LockableObjectBase {
@Lockable var someString: String = "Zork"
@Lockable var someInt: Int
override init() {
someInt = 42
// super.init() // Not needed in this particular example.
}
}
var testObject = DataObject()
testObject.isLocked = true
print(testObject.someInt, testObject.someString) // 42, Zork
testObject.isLocked = false // Has no effect: Object remained locked
print (testObject.isLocked) // true
testObject.someInt = 2 // Aborts the program
arsenius's answer here provided the vital reflection clue!