protocols & patterns but not types?

so I have this challenge that keeps coming up. I solve it each time, but it's messy. I'd like to do it better.


my classes derive from a protocol OR a superClass, but in this case it's a protocol.

and the reason I have a hierarchy at all is that the classes have to handle different data similarly.


so one of the central methods of the class (this is a for instance) is a method that converts the class's internal type to another type.

func convert()->String

a sibling class might need a similar method, but with a different return type:

func convert()->Int

these classes are required to have a common ancestor (because their instances will eventually be in the same array.)


But, I cannot define the 'convert' method in the protocol or the superclass, such that code addressing as yet unidentified specific type as it's superclass, can simply call the method:

let myVar = classInstanceTreatedAsProtocolOrSuperClass.convert()


I am instead, forced to do a switch:


switch superClassObject{

case is ClassType:

let myLet = (superClassObject as! ClassType).convert()

whole list of things to do once we get that value

case is OtherClassType:

all of literally the same code pattern with the potential for slight differences due to type.

...

}


at times like this, with the Overabundance of required if and if let statements just to get through this, Swift goes from fun, to nightmarishly wordy.


I would like to define, in my protocol, a method that defers the return type, but sets up teh method name, so that classes the partake of my protocol, can be expected to support that method. I don't think that's possible in swift. it may conflict with some of the behavior already in place... but I thoought I'd ask. It's no fun writing War and Peace, over and over again.

Just an idea, not fully tested but seem to work.


Let the protocol return Any

func convert()-> Any


And the real type being defined in implementations in each class

and called

let myVar = classInstanceTreatedAsProtocolOrSuperClass.convert() as! String // maybe safer to use as?

I "think" i may have tried that at one point.

my not-terribly-clear memory is that without a specifically typed return value, it becomes even more work. Once you set the return value to Any in the superClass, or the protocol, you have to use Any as the return value in the subclass. Otherwise any method that defines a different type is treated as an overloaded method, and not an overridden method. make sense? in other words, you get the ability to declare it in the superclass/protocol like I wanted, but you lose the benefit of being able to rely on the subclass for setting type. And that becomes a big headache on it's own.

What I was thinking was:

- effectively to declare Any in subClass,

- but return the real type (String) in the func imlplementation in the subclass


But maybe I missed some of your requirements.

The crux of this issue seems to be the return type of your

convert
method and its relationship to your clients. You wrote:
switch superClassObject {
case is ClassType:
    let myLet = (superClassObject as! ClassType).convert()
    whole list of things to do once we get that value
case is OtherClassType:
    all of literally the same code pattern with the potential for slight differences due to type
...
}

To start, you can write that code much more simply using pattern matching. Replace lines 2 and 3 with:

case let myObj as ClassType:
    let myLet = myObj.convert()

However, that’s just a minor improvement and doesn’t address your main concern. Rather, you main concern seems to be the duplication of code in lines 4 and 6.

One way to tackle that is to survey the requirements of that code and encapsulate that in a protocol. To turn this into a concrete example, consider this:

class Vehicle {
    func engine() -> Engine {
        fatalError()
    }
}

protocol Engine {
    var manufacturer: String { get }
    var cost: Int { get }
}

That is, there’s a vehicle class and every vehicle must be able to return an engine and every engine has a manufacturer and a cost. If the client gets a vehicle they can print the cost of the engine like so:

let engine = vehicle.engine()
print(engine.cost)

It’s still possible for the client to have engine-specific logic. For example, consider this:

class Jet: Vehicle {

    override func engine() -> Engine {
        return JetEngine(manufacturer: "RR", cost: 9_000_000_000, thrust: 280.0)
    }
}

struct JetEngine: Engine {
    var manufacturer: String
    var cost: Int
    var thrust: Double
}

A jet has a jet engine which has a thrust value. The client can deal with this on a one-off basis:

if let j = engine as? JetEngine {
    print(j.thrust)
}

Or use a switch to handle a whole slew of them:

switch engine {
case let j as JetEngine:
    print(j.thrust)
…
}

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
protocols & patterns but not types?
 
 
Q