1

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?

Dave
  • 1,081
  • 6
  • 10
  • 2
    You are declaring `myBool` as `String` rather than as `Bool`. I doubt this is intended. – vadian Feb 22 '18 at 11:12
  • 1
    No, you can't do this. You can only get/set associated objects on Obj-C objects, which `String` is not. This is probably the wrong design anyway (should *every* string really have a `myBool` property?), you may want to consider using a `struct` instead that has a `String` and a `Bool` property. – Hamish Feb 22 '18 at 11:17
  • Hi Hamish, Thanks for your response. Unfortunately, in my case wrapping the whole stuff is not a good solution. Of course, in other cases, it would work. BTW: I can add associated objects to Swift objects, which are not inheriting from NSObject. – Dave Feb 22 '18 at 11:22
  • @Hamish: It's not needed for all String has myBool, but for some cases, it is needed. And unfortunately, I can't subclass from String... – Dave Feb 22 '18 at 11:31
  • 2
    @Dave On Apple platforms (i.e those with Obj-C interop), Swift class objects are also Obj-C objects (classes that don't define superclasses are implicitly made to inherit from an Obj-C base class that conforms to `NSObjectProtocol`), and so are visible to the Obj-C runtime and can therefore be used with associated objects; so that's why that works. – Hamish Feb 22 '18 at 11:39
  • 1
    @Dave Could you please tell us a bit more about your concrete use case for wanting this? – Hamish Feb 22 '18 at 11:39
  • @Hamish I'm working on a framework and I would like to maintain a "state" internally and expose only a plain String. So, the users of the framework can use it as a normal String. – Dave Feb 22 '18 at 12:15
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/165640/discussion-between-dave-and-hamish). – Dave Feb 22 '18 at 12:15
  • @vadian: It's just a key for storing the Bool property. The only way it will be used is "AssociatedKeys.myBool". From this, I think it's unambiguous that it is a key and it is named after the property, I would like to store with this key. – Dave Feb 26 '18 at 08:18

1 Answers1

0

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
Dave
  • 1,081
  • 6
  • 10