skip to Main Content

I would like to create a set of objects that exhibit the following behavior:

  1. Each has a BOOL property — call it dataLocked — that is initially false.
  2. Each has a set of stored properties whose values may be set, but not read, whenever dataLocked == false.
  3. Those same stored properties may be read, but not set, whenever dataLocked == true
  4. 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

2

Answers


  1. Chosen as BEST ANSWER
    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!


  2. 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

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search