swift class property override question.

I have a class hierarchy. and ideally I would like to manage the class hierarchy without regard to which subclass I am dealing with.

But I have run afoul what appears to me to be a design limitation of Swift. I'm hoping wiser individuals will know how to get around this limitation.


my subclasses (NSObject subclasses, this is a Mac os Cocoa app) exist exclusively to manage a single property. each class, handles a different type. But the patterns and behavior are all the same...

for instance, subClass 01 manages a property:

var val : String

while subClass02 manages :

var val : Double


I have tried defining this property in the base class as :

var val : Any

and then overriding it in subclasses:

override var val : Double (etc...)


this of course always throws an error. But it also forces me into the awkward position of checking the class type every single time I have to interact with the "val" property:


if let numObj = item as? subClass01{
//do stuff

}else if let strObj = item as? subClass02{
// do literally the same exact stuff.. because it doesn't matter WHAT the type of the property is for what I'm trying to do.

}


I have many of these classes, and my code is getting very thick with all of this boilerplate crap. it's hard to follow at times, and it forces a number of conventions that lead to otehr limitations (such as... if I would like to add a new subclass to my hierarchy, I now have to find every instance where I've had to explicitly check for type, and add some code.)


is there _ANY_ way to add a property to a parent class, and then defer or override the property type in a sub class?


the things I have tried in the past:

1. there is some kind of deferred property in protocol definitions. can't find it right now, don't remember the name. doesn't matter. after working with it, it was clear that it behaves in exactly the oppisite way I need.

2. I've written a class with every possible permutation of value type that I require. Not only is it a ton of work, but it is even more difficult to manage from the outside.

3. I am currently using the class hierarchy approach with a novel KVO hack to manage notification of changes. I define a Bool in my base class, and when my subClass's value changes, I call self.willChangeValue(forKey: "change") and then self.didChangeValue(forKey: "change")... and in classes that need to know about the change, I can observe a single property. But I am still forced to write multiple lines of fragile code that test the class type and then run the exact same code...

Generics?


(There was a longer answer, but the **** forum is telling me about "invalid characters".)

I have had similar issues with the forum. understood. I'll look up Generics and get back to you.

I guess I don't see how Generics can be applied in this instance.


here's some real code


assume that inputObsrvr and outputObsrvr are of type NSKeyValueObservation?

and inProp, and outProp are of the hierachy discussed previously.

inProp and outProp both define a property : val, and are the same class, but the class can vary, resulting in val having a different type... Double, String, Boolean, or url. The base class for these classes does not define a val property because there's no way to override it and provide a type in subclasses, and that's the issue I am having.

Not defining a val property, there's no way I can see to employ generics. or rather: no point to utilizing generics because I still have to test the class to it's type in exactly the same way I already have.


from what I see in Generics, it can only address the parts where I set the values,a nd those parts of my code are as simple as they are getting.


of note: the property "change" that is being observed is defined in the base class and subclasses fake messages of it's update as the val property is updated. That hack makes KVO a little easier.


public func setupObservation(){
        inputObsrvr = inProp.observe(\.change , options: [.new, .old]){ (object, change) in
            if self.shouldDampenResponse == false{
                self.shouldDampenResponse = true /
                if let theIn = inProp as? CNNumberProperty, let theOut = outProp as? CNNumberProperty{
                    theOut.val = theIn.val
                }else if let theIn = inProp as? CNStringProperty, let theOut = outProp as? CNStringProperty{
                    theOut.val = theIn.val
                }else if let theIn = inProp as? CNSwitchProperty, let theOut = outProp as? CNSwitchProperty{
                    theOut.val = theIn.val
                }else if let theIn = inProp as? CNFileProperty, let theOut = outProp as? CNFileProperty{
                    theOut.val = theIn.val
                }
            }else{
                shouldDampenResponse = false
            }
        }
      
        outputObservr = outProp.observe(\.change , options: [.new, .old]){ (object, change) in
            if self.shouldDampenResponse == false{
                self.shouldDampenResponse = true /
                if let theIn = inProp as? CNNumberProperty, let theOut = outProp as? CNNumberProperty{
                    theIn.val = theOut.val
                }else if let theIn = inProp as? CNStringProperty, let theOut = outProp as? CNStringProperty{
                    theIn.val = theOut.val
                }else if let theIn = inProp as? CNSwitchProperty, let theOut = outProp as? CNSwitchProperty{
                    theIn.val = theOut.val
                }else if let theIn = inProp as? CNFileProperty, let theOut = outProp as? CNFileProperty{
                    theIn.val = theOut.val
                }
            }else{
                shouldDampenResponse = false
            }
        }
    }

It matters where "setupObservation" is. Is it a global function? A method of the base class? A method of some other class?


In the absence of those details, if the types of inProp and outProp are known at compile time, I would expect a local solution to look something like this:


public func setupObservation<T> (inProp: T, outProp: T) {
     inputObsrvr = inProp.observe (\.change, options: [.new, .old] { (object, change) in
          if self.shouldDampenResponse == false{
               self.shouldDampenResponse = true
               outProp.val = inProp.val
          }else{
               shouldDampenResponse = false
          }
     }
// … and so on …
}


If the types are not known at compile time (and couldn't be known just by rearranging other code), generics can't help. In that case I would use an enum-typed "val" property, with cases corresponding to CNNumberProperty, CNStringProperty, etc, and associated values. If all you do is move the values around, as in this method, you don't need any additional code. If you need to compare "val"s, then you can write an "==" function on the enum. If you need to do other things on "val"s of compatible type, you can write methods on the enum to do that all in one place. This would eliminate the need for subclasses, at least for the "val" reason — you might have them for other behavioral reasons.


But it's hard to recommend an approach in isolation. Generally in Swift, it's hard to adapt an approach that fit well into the Obj-C world. It's often easier to start with a radical redesign.

a method of some other class that works with 2 intsances of the base class.


but that solution doesn't work, because the base class does not define the val property. as I said: each subclass needs to have a different type for that property. and so it is not defined in the base class at all. forcing me to test the type.


hence why I'm posting here.


in response to the flat statement "But it's hard to recommend an approach in isolation. Generally in Swift, it's hard to adapt an approach that fit well into the Obj-C world. It's often easier to start with a radical redesign" I undertsand that you're working without context here. This is definitively NOT an issue caused by any of my reliance on Cocoa. It is a problem that ObjC would also have. I'll try to sum it up: I need to manage the base class, and manipulate different property types in precisely the same way. I have found that I cannot reference those property classes in the base class, and I cannot override (in the subclasses) a property that is defined in the base class, and alter it's type. So I have classes that work exactly the same, but force wildly overweight and limiting code in any part of the app that works with them. I've gotten so used to writing this code, that I can do it off the cuff... because I have to rewrite it very often, because any small change to the classes, any addition to the classes, invalidates code everywhere.


I am searching for a radical redesign option. It sounds like there aren't any.


I need my classes to have individual properties of various types.

my classes need to be stored in an array (which means they must be sibling classes with a base class)

the base class cannot have a property that can be overridden in some way to alter the type, so the code i write cannot assume the base class, it has to identify the individual class for each instance, in order to run the exact same code.


result: I am forced to write smelly code, in order to get the results I need.

there is a messy option. one that I am now toying with because of the limitations I'm experiencing.


in the base class, define a property:

var val : Any


and then in derived classes, instead of trying to override the val property write a getter/setter property:

@objc dynamic var strVal : String {

get{

return val as! String

}

set{

val = newValue

}

}


it's not going to be smelly, and it won't cause the fragile smelly code throughout the App, in quite the same way... but it will have some smelly code in other areas. I never liked it, it was the first solution I came up with, but if I'm being honest it is an option.

>> It is a problem that ObjC would also have.


In Obj-C, you could define the property in the base class as type 'id'. That would allow you to confirm that inProp and outProp have the same class (if that's what you need) or that inProp.val and outProp.val have the same class (if that's what you really want), then assign outProp.val to inProp.val without enumerating the classes involved, or even casting anything. In fact, you can do basically the same thing in Swift, but it's just not very Swifty.


What's wrong with the enum approach? It's the Swiftier version of the above, I suppose.


My guess is that a generic class in place of the subclasses and a protocol in place of the base class would lead to a more satisfying solution in Swift. However, as I said before, it'd likely be a radical redesign, and it might take several iterations to get there. I could understand why anyone might not be prepared to embark on that.

"My guess is that a generic class in place of the subclasses and a protocol in place of the base class would lead to a more satisfying solution in Swift. However, as I said before, it'd likely be a radical redesign, and it might take several iterations to get there. I could understand why anyone might not be prepared to embark on that."


QuinceyMorris, I have done that. It's abysmal.

You wind up writing a very complex class, AND very complex smelly code to keep it from imploding. I've written a handful of permutations of this system. I HAVE embarked on it. While the needs have mutated over time, one thing has always been true: programming languages don't make this easy. the single thing that these classes have in common is the one thing that cannot be treated as the same thing.


As for the enumeration maxi-class approach, It's a dead end. I want to write code that doesn't screw up all the code I haven't written yet. And that's exactly what happens. You get a class that forces you to write discovery code for it every time you interact with it. the same thing I'm trying to get out of now.


I haven't set any deadlines. I've given myself the time it will take, and when I learn, I apply that learning. Maybe some day down the road... a single class with a fistfull of internal types will make sense. Right now it's a load of boiler plate code, and even more boiler plate code just pushed off into the future.


here's my hard won lessons:

when writing for the desktop do not use structs. use classes that descend from NSObject.

Protocols are nice, do not start with protocols. Start with an NSObject descendant.

use protocols to add behavior (and relationships) to classes that are otherwise unrealted.


of course that's not swifty. Cocoa and the rest of the OS X Frameworks... they aren't swifty. The name of the game is to do anything that reduces the load on the guy writing the code. There's no point in volunteering to do things the hard way.


here's the solution that I have adopted... and frankly... it has issues, but it's probably the least smelly version yet. it's the third time I'm trying this approach, the last time was way back in Swift 2, and back then I think I started out with a tragic separation of responsibilities between classes, which made for an overweight class, that had to be carried throguhout all subclasses, and managed in all interactions.


the baseClass defines a property

var val : Any


the child classes define a second property

var strVal : String

this is a synthetic property with a getter setter.

get{ return val as! String}

set{ super.val = newValue}


both properties are setup as @objc dynamic so in interface builder you can bind to the typed property, and in the code you can bind to the base class untyped property. Now, I don't have to check the class so that I can do generic operations with that property. I won't be doing any big transofrmations at that level, but I can set a value, and I can observe it. When saving, I just save the val : Any property. no need to worry about type, because I cast it in my subclasses on demand.


The subclasses of the various subclasses seem to handle it well. I've added some new classes that had requirements that originally triggered this refactoring, and now I'm updating the classes that interact with these classes, and when i get through that, we'll see if it works. It's a lot of classes. I don't know that there is a right answer to this design question, so there might never be a "correct answer" in this thread.

swift class property override question.
 
 
Q