Using Native Swift to make Units Conversions

Swift to make Units Conversions head image example

Hallo mijn vrienden, Leo Hier. Today topic is how to use Swift to make Units Conversions and improve your code readability.

Today we will explore something that I learn two weeks ago.

I was wondering if there’s a native way to convert hours to seconds or even kilometers to meters. The good thing is that Apple already thought that for us and we don’t need to re-implement those functions.

You will learn how to use the `Measurement` generic struct, and how this can be handy in your day-to-day life. Unfortunately, it is only for iOS 10 and above.

Disclaimer: You should keep in mind that as a Foundation API, you don’t have control over it. So if you need very strict control of the API’s behavior you should *do it yourself*. Of course, you can decrease the *Apple factor risk* covering your APIs with unit tests, so if something default changes, you are prepared and the production code will not explode.

Let’s code! But first…

 

The Painting of The Day

Today’s painting called Woman Pouring Water into a Jar is a 1640 piece from Gerrit Dou. Gerrit Dou (7 April 1613 – 9 February 1675), also known as Gerard and Douw or Dow, was a Dutch Golden Age painter, whose small, highly polished paintings are typical of the Leiden fijnschilders. He specialized in genre scenes and is noted for his trompe l’oeil “niche” paintings and candlelit night scenes with strong chiaroscuro. He was a student of Rembrandt.

From Rembrandt, he acquired his skill in coloring and the more subtle effects of chiaroscuro, and his master’s style is reflected in several of his earlier pictures.

I chose this picture because with the knowledge within this post you can create a converter that can convert a jar of water into a bowl of water, and this painting has a jar of water that can be converted into a bowl of water. Got it?

 

The Problem – Swift to make Units Conversions

You need to make a unit conversion process for a cryptocurrency module and you don’t want to make an in-house solution for that.

You can always try to find a [cocoapod](https://github.com/materik/cocoapod-unitility) that does that for you. But that’s not what we intended here. I wish that we have a native Swift way of unit conversion. And I gladly found this: the [Measurement struct](https://developer.apple.com/documentation/foundation/measurement). It is a generic struct part of the Foundation framework, so it’s guaranteed you already have it in your codebase.

Let’s deep dive into it and see the capabilities.

 

Measurement Definition

The overview from Apple is that object represents a quantity and unit of measure. Both concepts are fundamental when you are dealing with unit conversion because one says what is and the other says about how much of that we have.

Additionally Measurement objects provide a programmatic interface to converting measurements into different units, as well as calculating the sum or difference between two measurements. Measurements support a large set of operators, including +, -, *, /, and a full set of comparison operators. All those traits make this API very user-friendly and desired against third-party or in-house solutions since we have it for free with Foundation.

The way to initialize is using a `Unit` object and double value. And the last thing is that Measurement objects are immutable, and cannot be changed after being created, which is good because having to manage states between conversions it’s not what we could call “handy”.

 

Code Measurement Example

Remember that we said earlier you should start your measurement with a type and a quantity?

The example is here:

var seconds = Measurement<UnitDuration>(value: 7200, unit: .seconds )
print("Unit before conversion -> ", seconds)

print()

let hours = seconds.converted(to: .hours)
print("Unit after conversion -> ", hours)
print("Measurement value -> ", hours.value)
print("Measurement unit symbol ->", hours.unit.symbol)

Resulting in:

Swift to make Units Conversions image example first image result converting seconds in to hours

Before further API exploration, we’ll deep dive into that UnitDuration type and how it can do all of the conversions for us.

 

Deep dive into Measurement

You must be asking yourself: “But how the Measurement knows how to convert all units with only that API???”. Its because Measurement is leveraged by the power of generics.

When you are writing code that has the same structure and the same functions just changing the meaning of those structures, you have in your hands a great candidate for the use of Generics.

Looking for the Measurement implementation is defined as:

public struct Measurement<UnitType> : ReferenceConvertible, Comparable, Equatable where UnitType : Unit {
    
    public typealias ReferenceType = NSMeasurement
    
    public let unit: UnitType
    public var value: Double
    
    public init(value: Double, unit: UnitType)
    
    public func hash(into hasher: inout Hasher)
    public var hashValue: Int { get }
}

Look at both of the attributes that every measurement has, the unit and the value, in other words, the “what” and “how many”. Also here we the UnitType appearance that is a Unit. But wait… what is a Unit? Each instance of a Unit subclass consists of a symbol, which can be used to create string representations of NSMeasurement objects with the MeasurementFormatter class.

Let’s see:

@available(iOS 10.0, *)
open class Unit : NSObject, NSCopying, NSSecureCoding {
    
    open var symbol: String { get }
    public init(symbol: String)
}

Good, now we know every symbol or the Unit comes from. But now… where’s the convert function come from? Well, it is a type specialization of Measurement.

Check below:

@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
extension Measurement where UnitType : Dimension {
    
    public func converted(to otherUnit: UnitType) -> Measurement<UnitType>
    public mutating func convert(to otherUnit: UnitType)
    public static func - (lhs: Measurement<UnitType>, rhs: Measurement<UnitType>) -> Measurement<UnitType>
    public static func + (lhs: Measurement<UnitType>, rhs: Measurement<UnitType>) -> Measurement<UnitType>
    
}

So the Measurement type have the convert and converted functions. The convert function is done in place, this means you mutate the unit and the value of the Measurement and the converted you create a new Measurement type with your converted type. You should use what suits you better.

Wait a minute, what is the Dimension type? And how does the measurement type know how to convert every generic type of Unit?

 

The Dimension Type

Dimension is not the Unit or it is? As you are already guessing it must be a type of Unit. And now we came to the last piece of our puzzle, it’s a generic way to convert all the units with one structure, beautiful.

Check it out:

open class Dimension : Unit, NSSecureCoding {
    
    @NSCopying open var converter: UnitConverter { get }
    
    public init(symbol: String, converter: UnitConverter)
    
    open class func baseUnit() -> Self
}

All the Dimension objects have an UnitConverter object within. That is responsible to do all the conversions! But how it does do the conversions? The UnitConverter is the answer:

@available(iOS 10.0, *)
open class UnitConverter : NSObject {
    open func baseUnitValue(fromValue value: Double) -> Double
    
    open func value(fromBaseUnitValue baseUnitValue: Double) -> Double
}

Each instance of a Dimension subclass has a converter, which represents the unit in terms of the dimension’s `baseUnit()`. For example, the `NSLengthUnit` class uses meters as its base unit. The system defines the predefined miles unit by a UnitConverterLinear with a coefficient of 1609.34, which corresponds to the conversion ratio of miles to meters (1 mi = 1609.34 m); the system defines the predefined meters unit by a UnitConverterLinear with a coefficient of 1.0 because it’s the base unit.

The final puzzle is: You create a Measurement object that has a quantity and a UnitType, if that unit type is a Dimension it enables you to convert any unit within that UnitType.

 

Unit Conversion Types

The Foundation Framework brings a lot out of the box Dimension objects to play with they are, but are not limited to:

  1. UnitAcceleration – With metersPerSecondSquared and gravity attributes.
  2. UnitAngle – That has degrees, radians etc properties.
  3. UnitArea – That has squareMeters , acres, squareInches etc properties.
  4. UnitConcentrationMass – That has gramsPerLiter, milligramsPerDeciliter properties
  5. UnitDuration – That has hours, minutes, secods etc

And many more! Check the docs to see them all.

 

Create your Custom Units with Measurement

Finally, let’s answer the question from the beginning and create our Cryptocurrency Converter. Let’s say you will provide only three cryptocurrencies: bitcoin, Ethereum, and Tron. (This is just an example, not an investment recommendation).

You only have to implement the Dimension protocol and override the baseUnit function, and you are good to go! Copy/paste the code below into your playgrounds:

final class CryptoCurrency: Dimension {
    static let bitcoin = CryptoCurrency(symbol: "BTC",
                                        converter: UnitConverterLinear(coefficient: 1.0))
    static let ethereum = CryptoCurrency(symbol: "ETH",
                                         converter: UnitConverterLinear(coefficient: 1/15.22))
    static let tron = CryptoCurrency(symbol: "TRX",
                                     converter: UnitConverterLinear(coefficient: 1/659943.0))
    
    override class func baseUnit() -> CryptoCurrency {
        return bitcoin
    }
}

Here we are setting up the three supported currencies. To create a new CryptoCurrency you just need to provide the symbol and the converter. In our case, everything will be indexed to bitcoin, and it’s inversion proportional so the ratio is one divided by the proportion. In the real world, this coefficient would change so you would have more logic on it, but you get the point.

To test let’s create some bitcoins and convert them:

let bitcoins = Measurement<CryptoCurrency>(value: 3, unit: .bitcoin)
print("Bitcoins before conversion -> ", bitcoins)

print()
let ethereum = bitcoins.converted(to: .ethereum)
print("Ethereum Unit after conversion -> ", ethereum)
print("Ethereum Measurement value -> ", ethereum.value)
print("Ethereum Measurement unit symbol ->", ethereum.unit.symbol)

print()
let tron = bitcoins.converted(to: .tron)
print("Tron Unit after conversion -> ", tron)
print("Tron Measurement value -> ", ethereum.value)
print("Tron Measurement unit symbol ->", ethereum.unit.symbol)

Resulting in:

this image demonstrates the result of using Measurement to convert units of cryptocurrencies

And we are done!

 

Summary – Swift to make Units Conversions

Today we studied the Measurement API and how it can be flexible to your needs. You can use the default Dimensions and Units as well as build your own Dimension to accomplish your tasks!

That’s all my people, I hope you liked reading this article as much as I enjoyed writing it. If you want to support this blog you can Buy Me a Coffee or leave a comment saying hello. You can also sponsor posts and I’m open to freelance writing! You can reach me on LinkedIn or Twitter and send me an e-mail through the contact page.

Thanks for reading and… That’s all folks.

Credits:

title image

Share this post:

Related posts

Sponsor