Search code examples
swiftstringstructassociated-object

Can I add associated object to Swift Struct?


I would like to add an additional property to the Swift String. I used this approach few times on objects, but it seems that it does not work on struct. Although, I don't get any error...

This is what I tried:

var str = "Hello, StackOverflow"
fileprivate struct AssociatedKeys {
    static var myBool = "myBool"
}

extension String {
    public var myBool: Bool {
        get {
            guard let myBoolObject = objc_getAssociatedObject(self, &AssociatedKeys.myBool) as? NSNumber else {
                return false
            }
            return myBoolObject.boolValue // execution never reaches this line
        }

        set(value) {
            let object = NSNumber(value: value)
            objc_setAssociatedObject(self, &AssociatedKeys.myBool, object, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

str.myBool = true

print(str.myBool)  // prints "false"

It prints out that it is false.

At first, I tried it without wrapping the Bool into NSNumber, but the result was the same.

Is this even possible to add an associated object to a struct at all? If not, can anyone tell me why?


Solution

  • Based on @Hamish's comment, I created the following solution to workaround the issue. Preconditions:

    • Have a framework which proposes prefilled objects, the app works on these objects and the framework should know which of the properties are modified during the processing of this object later.

    • Not using looooong initializers to setup all properties of MyObject is a design decision.

    In my example, the usage of the myObject is a dummy and shows what happens in the framework and what happens in the app.

    // protocol is used, as we could handle more modifiable structs/classes in a common way
    protocol PropertyState {
        var isModified: Bool {get set}
    }
    
    internal struct ModifiableString : PropertyState {
        var string: String
        var isModified: Bool
    }
    
    class MyObject: PropertyState {
        internal var _name = ModifiableString(string: "", isModified: false)
        public var name: String {
            get {
                return _name.string
            }
            set(value) {
                _name.string = value
                _name.isModified = true
            }
        }
    
        // + N similar properties (they can be other types than String, by implementing other structs, like ModifiableBool)
    
        var isModified: Bool {
            get {
                return _name.isModified // || _myAnotherProperty.isModified
            }
            set(value) {
                _name.isModified = value
                // _myAnotherProperty.isModified = value
            }
        }
    }
    
    // internal filling of the object inside of the framework
    let myObject = MyObject()
    myObject.name = "originalValue"
    print(myObject.isModified)   // true
    // filling the object with values ended, so we can set the state
    myObject.isModified = false
    print(myObject.isModified)   // false
    
    // the app can work with the object
    // let myObject = Framework.getObject()
    myObject.name = "modifiedValue"
    
    // now the framework should now which properties are modified
    print(myObject._name.isModified)   // true