Jbryson - 1 year ago 344
Swift Question

# Swift protocol extensions with enums

So I have several enums representing various unit systems:

``````enum MassUnit:Double{
case Pound = 453.59237, Ounce = 28.349523125, Gram = 1.0, Kilogram = 1000.0;
}
enum VolumeUnit:Double{
case Teaspoon = 1, Tablespoon = 3, Cup = 48, Pint = 96, Quart = 192, Gallon = 768, Liter = 202.884136211, Milliliter = 0.202884136211
}
enum TimeUnit:Double{
case Second = 1, Minute = 60, Hour = 3600, Day = 86400, Week = 604800, Year = 31536000
}
``````

What I would like to do is be able to convert from one unit to another, eg from year to seconds. To do this I've ensured that the raw values for my enums correspond to the converting multipliers, eg 1 Min = 60 seconds. Thus, given x amount of some unit, the conversion is simply

``````x * rawValue1 / rawValue2 // rawValue2 = rawValue of desired unit.
``````

While that conversion is simple enough, I would love to be efficient and use a protocol:

``````protocol Convertable{
func convert(inputAmount inputAmount:Double, outputUnit:Self)->Double;
}
``````

Then, I could extend the enum:

``````extension TimeUnit:Convertable{
func convert(inputAmount inputAmount: Double, outputUnit: TimeUnit) -> Double {
return inputAmount * self.rawValue / outputUnit.rawValue;
}
}
``````

Then I could simply convert like this:

``````TimeUnit.Year.convert(inputAmount: 2.54, outputUnit: .Second)
// returns 80101440
``````

This is great, however, depending on how many units I want to convert, there would be a lot of duplication of the same code.

So, what I would like to do is somehow use a protocol extension.

``````extension Convertable{
func convert(inputAmount inputAmount: Double, outputUnit: Self) -> Double {
return inputAmount * self.rawValue / outputUnit.rawValue;// Compile error...
}
}
``````

This is where I get into trouble, the output unit is declared as self, which knows nothing about rawValue.

Any ideas?

Right when I was asking this question, the answer came to me: Have the Convertible protocol also require a rawValue variable:

``````protocol Convertable{
var rawValue:Double{get}
func convert(inputAmount inputAmount:Double, outputUnit:Self)->Double;
}
``````

That way, referencing `self.rawValue` is known, and since enums already have a rawValue, no extra work is required:

``````enum MassUnit:Double, Convertable{
case Pound = 453.59237, Ounce = 28.349523125, Gram = 1.0, Kilogram = 1000.0;
}

enum VolumeUnit:Double, Convertable{
case Teaspoon = 1, Tablespoon = 3, Cup = 48, Pint = 96, Quart = 192, Gallon = 768, Liter = 202.884136211, Milliliter = 0.202884136211
}

enum TimeUnit:Double, Convertable{
case Second = 1, Minute = 60, Hour = 3600, Day = 86400, Week = 604800, Year = 31536000
}
``````

And then using the converter:

``````TimeUnit.Year.convert(inputAmount: 2.54, outputUnit: .Second) // 80101440
MassUnit.Gram.convert(inputAmount: 20.0, outputUnit: .Ounce) //0.70547
VolumeUnit.Pint.convert(inputAmount: 0.2, outputUnit: .Tablespoon)// 6.4000
``````

Even beter, in the odd case where a conversion doesn't work like this, I can override the convert function with my own implementation, such as in Temperature:

``````enum TemperatureUnit:Double, Convertable{
case Celsius, Fahrenheit

func convert(inputAmount inputAmount: Double, outputUnit: TemperatureUnit) -> Double {
if self == outputUnit {
return inputAmount;
} else if self == .Celsius {
return inputAmount * 9.0/5.0 + 32.0
} else {
return (inputAmount - 32.0) * 5.0 / 9.0;
}
}
}

TemperatureUnit.Celsius.convert(inputAmount: 3, outputUnit: .Fahrenheit) // 37.4
TemperatureUnit.Fahrenheit.convert(inputAmount: 0, outputUnit: .Celsius) // -17.7778
``````

Beautiful!

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download